<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Emil Lerch</title>
    <link>https://emil.lerch.org/</link>
    <description>Recent content on Emil Lerch</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Fri, 09 Jan 2026 14:43:51 -0800</lastBuildDate><atom:link href="https://emil.lerch.org/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>wttr.in</title>
      <link>https://emil.lerch.org/wttr.in/</link>
      <pubDate>Fri, 09 Jan 2026 14:43:51 -0800</pubDate>
      
      <guid>https://emil.lerch.org/wttr.in/</guid>
      <description>&lt;h1 id=&#34;wttrin&#34;&gt;wttr.in&lt;/h1&gt;
&lt;p&gt;This is another heavy AI-assisted coding post&amp;hellip;sorry about that. But, while
AI is featured in the story, it&amp;rsquo;s not the point of the story. For a long time,
I have been a fan of &lt;a href=&#34;https://github.com/chubin/wttr.in&#34;&gt;wttr.in&lt;/a&gt;. I&amp;rsquo;m posting
the github link here instead of the production site, mostly because it takes
5-10 seconds for me recently (I don&amp;rsquo;t remember this before?). The concept is
really cool (who doesn&amp;rsquo;t like weather you can use from your terminal). I cloned
it with the intention of self-hosting it, and&amp;hellip;learned a lot.&lt;/p&gt;
&lt;h2 id=&#34;installing&#34;&gt;Installing&lt;/h2&gt;
&lt;p&gt;So, the intro of the readme says in part:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Originally started as a small project, a wrapper for wego, intended to demonstrate the power of the console-oriented services&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That shows. Here&amp;rsquo;s the (AI-generated) request flow from wttr.in. It actually get&amp;rsquo;s a bit more involved than even this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Client Request
    ↓
Go Proxy (port 8082) - LRU cache + prefetch
    ↓ (cache miss)
Python Backend (port 8002) - Flask/gevent
    ↓
Location Resolution (GeoIP/IP2Location/IPInfo)
    ↓
Weather API (met.no or WorldWeatherOnline)
    ↓
Format &amp;amp; Render (ANSI/HTML/PNG/JSON/Prometheus)
    ↓
Response (cached with TTL 1000-2000s)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So, we have a go program, with it&amp;rsquo;s own cache. Then a python backend, which, if
memory serves, also has a cache. However, depending on the format you&amp;rsquo;re
looking for, it&amp;rsquo;s either a) rendering itself, or b) kind of sort of skips all
this and just calls a fork of &amp;ldquo;wego&amp;rdquo;, which, as mentioned in the readme, this
was originally the plan as a wrapper. All that other stuff kind of organically
grew over time. Self hosting ultimately wasn&amp;rsquo;t terrible, but it wasn&amp;rsquo;t pleasant
either. Several API keys, and paid services, etc. Also, two separate sets of
caching layers, each in their own directory, made creating a dockerfile&amp;hellip;well,
weird.&lt;/p&gt;
&lt;h2 id=&#34;a-hard-blocker&#34;&gt;A hard blocker&lt;/h2&gt;
&lt;p&gt;Once installed, I ran into a firm block for me. The default (only at the time I
think?) service was a paid weather service. That makes no sense for me as a
single user. So, I put my ~money~ time where my mouth is, and
&lt;a href=&#34;https://github.com/chubin/wttr.in/pull/532&#34;&gt;picked up the pen&lt;/a&gt; to implement
met.no as a weather source. They don&amp;rsquo;t even require an API key, just &amp;ldquo;be nice&amp;rdquo;,
which is awesome. I didn&amp;rsquo;t realize about this wego thing, so while met.no worked
fine for my purposes, the full rendering was always different from the custom
rendering that I was using, and I never really bothered to track it down. In the
last 3 weeks, I&amp;rsquo;ve learned that&amp;rsquo;s because wego is a very different pipeline,
and uses its own web service configuration!&lt;/p&gt;
&lt;h2 id=&#34;5-years-go-by&#34;&gt;5 years go by&lt;/h2&gt;
&lt;p&gt;This has all been bugging me for&amp;hellip;well, 5 years. On my long term todo list
was to help clean this organically grown architecture and simplify it, make
it self-hosted friendly, etc. So I find myself with some spare time over Christmas
break, and I have a codebase and an AI to help do a lot of typing. So I ask the
AI to build a plan based on a new, simplified architecture, and we work together
to come up suitable. It&amp;rsquo;s original estimate was 10 weeks for a full rewrite.
I have a few missing features (i18n, PNG support, Moon endpoint&amp;hellip;but moon phase
&lt;em&gt;is&lt;/em&gt; supported, and wttr.in compatible json, as my json is a met.no passthrough).&lt;/p&gt;
&lt;h2 id=&#34;starting-out&#34;&gt;Starting out&lt;/h2&gt;
&lt;p&gt;I asked the AI for a plan. I wanted this built in zig (of course). I wanted a
vastly simplified architecture. At the end of the day, a single caching layer
(vs Go+python each with their own) in a single directory. Everything will be
self-contained, with the goal as a single static binary. Spoiler: I&amp;rsquo;ve achieved
this, even with the first pass of png support. I wanted a coherent organized
code base (still some rough edges there imo), and critically, I wanted unit
tests everywhere. The AI made up a goal of 80% test coverage, and my coverage
report currently says I&amp;rsquo;m at 85%. I had it document the current architecture
and the target architecture, so I could argue and eventually just edit the
target architecture for future use. Then, I let it rip.&lt;/p&gt;
&lt;h2 id=&#34;first-pass-6hrs&#34;&gt;First pass (6hrs)&lt;/h2&gt;
&lt;p&gt;I got something functional, but there was a lot of cleanup to be done. In 6 hours
of work, mostly me arguing with the AI, I had a full working (but admittedly
buggy, and with structural deficiencies) implementation of most of the main
features. This was literally &amp;ldquo;close enough for government work&amp;rdquo;, that I felt
able to delete all the python/go in the directory to focus on the zig
implementation. Any other discrepancies, I&amp;rsquo;d compare to the live &lt;a href=&#34;https://wttr.in/&#34;&gt;https://wttr.in/&lt;/a&gt;
site or pull up the github repository. And I have to say, the live site&amp;hellip;I&amp;rsquo;m
not sure if it&amp;rsquo;s a result of the architecture, zig, or simply that I&amp;rsquo;m only
serving 1 req/sec, but the performance difference between this code and the
live site really made me reluctant to check out the live site unless absolutely
necessary.&lt;/p&gt;
&lt;h2 id=&#34;refining-15hrs&#34;&gt;Refining (15hrs)&lt;/h2&gt;
&lt;p&gt;My process for AI use on greenfield is often like this. Spend 5-10 minutes
getting the AI and I on the same page for what needs to be done. Then let it
write whatever it wants, just make it work. Then start in on the more detailed
work, where I still use AI (more later), but I start getting a lot more hands-on.
Typically, I think of this the same way I imagine a carpenter would. Chainsaws,
then more specific tools, followed by sanding, staining, etc. This initial
pass at this was about 15 hours. Life then intervened, and I took a 2 week
break.&lt;/p&gt;
&lt;h2 id=&#34;finishing-up-1-week&#34;&gt;Finishing up (1 week)&lt;/h2&gt;
&lt;p&gt;After the break, I spent a few hours every day for about a week, and wrapped
it up. At this point, AI was still heavily assisting me, but I was no longer
checking in any code that I didn&amp;rsquo;t code review and hand-edit to something I would
have authored directly. I literally said to myself on more than one occasion,
&amp;ldquo;this works, but it doesn&amp;rsquo;t look like code &lt;em&gt;I&lt;/em&gt; would write&amp;rdquo;, and I&amp;rsquo;d change
it. Sometimes I&amp;rsquo;d get into heated arguments with the AI, to the point where
once my wife came downstairs and asked why I was typing SO DAMN LOUDLY. I had to
tell her that was me yelling at the AI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Things that were super helpful:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Large refactorings: Once all the unit tests were in, the AI could cut through
these like butter, but I did have to make sure it was very clear when it could
and could not adjust test expectations. An easy way to get tests to pass is
to just adjust the expectations!&lt;/li&gt;
&lt;li&gt;Suggestions on where to start: I don&amp;rsquo;t know anything about calculating moon
phases or dawn/dusk/sunrise/sunset. The AI gave me a good overview, then
pointed me to C code in both cases. And it was able to quickly integrate them
into the project for me.&lt;/li&gt;
&lt;li&gt;Giving me multiple suggestions. Not sure of a design approach, or a name? Ask
the AI for 3. Ask it to explain the tradeoffs of each, and what its suggestion
is. I took the suggestion only maybe half the time, but the process helped me
think through everything.&lt;/li&gt;
&lt;li&gt;Doing a lot of work quickly. Even if I know it&amp;rsquo;s going to be terrible, I&amp;rsquo;d
rather have it write 20 unit tests, and then go fix the &lt;code&gt;std.testing.expect(response != null)&lt;/code&gt;
than to have to write all the boilerplate and setup stuff 20 times myself.
Keep in mind here, that part of writing the tests might be (sometimes large)
refactoring to components to allow for things like test mocks, or to separate
things doing IO from pure functions. I knew that whenever the AI finished,
the build and the tests would succeed, and I would just play quality control.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Where I struggled:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Visual stuff: It was easy to get it to get something visible, but getting it
&lt;em&gt;right&lt;/em&gt; was a whole different matter. I had a day-long argument about whether
monospaced ascii was aligned when it include the lighting bolt (⚡) character.
Even when I provided the string in a test expectations, it just refused.
Eventually, I got it to tell me why, and it&amp;rsquo;s not for the reason you might
expect (multi-byte unicode characters). No, it boiled down to &amp;ldquo;crappy terminals
don&amp;rsquo;t know how to display this character, so it should take up two cells.
Therefore, the right thing to do is to remove a padding space&amp;rdquo;. ok, wow&amp;hellip;
so after telling it that I don&amp;rsquo;t care about buggy terminals, that&amp;rsquo;s on them,
it was able to align things correctly. Once all the proper mocking code was
in place and I had (hand coded) the proper unit tests, this problem went
away.&lt;/li&gt;
&lt;li&gt;Adding new zig dependencies: This is a nit, but it consistently screws up
a simple &lt;code&gt;zig fetch --save&lt;/code&gt;. After this bit me twice (noticed it in CI), I
learned I could do the command myself much more quickly than having it botched
and either not catching it or having to argue about it.&lt;/li&gt;
&lt;li&gt;Style: The AI really wanted to make subdirectories. It really liked to do
things like have a &lt;code&gt;geoip.zig&lt;/code&gt; file with a &lt;code&gt;GeoIp&lt;/code&gt; struct inside it (the
zig way is to call it &lt;code&gt;GeoIp.zig&lt;/code&gt;, and the file &lt;em&gt;becomes&lt;/em&gt; the struct, but the
AI struggles with that concept. The AI really wants to put braces around
everything.&lt;/li&gt;
&lt;li&gt;Code duplication: I&amp;rsquo;m not a hard core DRY person, but the AI thinks nothing
of writing the same code 15 times in 15 different places. This can introduce
a lot of specific bugs and sometimes lead to dead code. I&amp;rsquo;m still thinking
of the three different caches in the system, which each have their own
implementation, and wondering if it&amp;rsquo;s appropriate. I think it is, as each
have unique aspects to the data, but I&amp;rsquo;m not 100%.&lt;/li&gt;
&lt;li&gt;Terrible test expectations: This was just consistently terrible, and eventually
&lt;em&gt;my&lt;/em&gt; expectations were that for unit tests I would just need the AI to blast
out some stuff, then I would do the final work on asserting the right
expectations myself.&lt;/li&gt;
&lt;li&gt;Ignoring instructions: Sometimes the AI would simply ignore me, or not do
the research (file reading) I would have expected it to. This was the most
frustrating thing, and I think it boils down to &amp;ldquo;my context was getting too
long&amp;rdquo;. I learned to restart my session, and keep sessions focused on one task.
If I asked it to refactor something, add in two different pieces of new
functionality, I would be in for trouble.&lt;/li&gt;
&lt;li&gt;Systems work: This was a user-facing application, and there is a lot of prior
art for application-level stuff, so I had a pretty good experience. But
whenever I use AI for systems level things (e.g. unikernels), it goes bad
in a hurry. I don&amp;rsquo;t even try much there.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;conclusions-and-future-work&#34;&gt;Conclusions and future work&lt;/h2&gt;
&lt;p&gt;This was a good experience, and I&amp;rsquo;m really excited to do almost a full rewrite
in about 2 weeks. I have a docker image for this, that is from the scratch
image with two files - the binary and TLS certificates. Adding PNG support, the
biggest remaining feature, looks to retain these properties. My prompt for
the png support literally started with &amp;ldquo;What would you suggest for png generation?&amp;rdquo;,
and in 30 minutes I have something working (to be clear, the output is terrible).
I suspect I&amp;rsquo;ll learn a lot about PNGs in the next few days, but I am certain I&amp;rsquo;ll
finish this feature a lot faster than I would have before AI. I will also,
of course, make sure the owner of wttr.in knows about this in case there&amp;rsquo;s
any interest in&amp;hellip;a full rewrite of the code into a language they&amp;rsquo;ve likely
never used?&lt;/p&gt;
&lt;p&gt;Oh&amp;hellip;and the repo is at &lt;a href=&#34;https://git.lerch.org/lobo/wttr&#34;&gt;https://git.lerch.org/lobo/wttr&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You can try it out at &lt;a href=&#34;https://wttr.lerch.org/&#34;&gt;https://wttr.lerch.org/&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Building an offline, limited version of Alexa</title>
      <link>https://emil.lerch.org/building-an-offline-limited-version-of-alexa/</link>
      <pubDate>Thu, 16 Oct 2025 12:50:20 -0700</pubDate>
      
      <guid>https://emil.lerch.org/building-an-offline-limited-version-of-alexa/</guid>
      <description>&lt;h1 id=&#34;building-an-offline-limited-version-of-alexa&#34;&gt;Building an offline, limited version of Alexa&lt;/h1&gt;
&lt;h2 id=&#34;or-how-belkin-and-alexa-conspired-to-nerd-snipe-me&#34;&gt;(or how Belkin and Alexa conspired to nerd snipe me)&lt;/h2&gt;
&lt;p&gt;It all started when Belkin decided to &lt;a href=&#34;https://www.belkin.com/support-article/?articleNum=335419&#34;&gt;end wemo support&lt;/a&gt;.
I am leery of things that require cloud support, whether they are &lt;a href=&#34;https://www.xda-developers.com/playable-canceled-games-that-leaked-online/&#34;&gt;games&lt;/a&gt;,
or &lt;a href=&#34;https://support.google.com/googlenest/answer/16233096?hl=en&#34;&gt;devices&lt;/a&gt;. I get that
business changes and these require ongoing investment from the company&amp;rsquo;s perspective,
but my interests are unique and different from company interests, and I&amp;rsquo;m not into
obsolescence, especially of physical devices, that work just fine.&lt;/p&gt;
&lt;p&gt;There is a disturbing trend of requiring more and more Internet access for things
that really don&amp;rsquo;t need to, and I actively push against this in most cases. There
is no reason that I should need my Internet connection to be up and operable to
turn on/off my lights! The original Belkin Wemo was exciting, because it could
operate locally and &lt;a href=&#34;https://www.amazon.com/b?node=21576558011&#34;&gt;Amazon Alexa&lt;/a&gt;,
though it is cloud-based, will connect to these devices without round tripping
to the cloud. If my Internet connection goes out, I still have a physical light
switch, and I could even use my phone with &lt;a href=&#34;https://www.home-assistant.io/&#34;&gt;Home Assistant&lt;/a&gt;.
Under normal operation, I don&amp;rsquo;t want Home Assistant in the mix, because it&amp;rsquo;s
another point of failure, and the Home Assistant/Alexa integration utilizes
Home Assistant cloud, so now I need an Internet connection and 2 online services
to be operable&amp;hellip;for my lights?!&lt;/p&gt;
&lt;p&gt;Anyway, for a long time I was in a pretty happy state. I had local control of
many lights in my house, Alexa could control them, home assistant could monitor
and control them, and all was good. My main problem was getting new Wemo devices,
because the later generations of Wemo dropped the local support altogether, and
at least once I ended up paying 3x the price of the Internet-only device to get
an older device shipped to me. Later, I discovered &lt;a href=&#34;https://github.com/arendst/Tasmota&#34;&gt;Tasmota&lt;/a&gt;,
which can pretend it is a Belkin Wemo device, and &lt;a href=&#34;https://cloudfree.shop/&#34;&gt;CloudFree&lt;/a&gt;,
an online shop that ships pre-configured Tasmota-based devices, and about
half my light switches (and holiday light setups) use devices from them.&lt;/p&gt;
&lt;h2 id=&#34;conspiracy-theories-start-here&#34;&gt;Conspiracy Theories Start Here!&lt;/h2&gt;
&lt;p&gt;With this background, the news of Belkin&amp;rsquo;s removal of Wemo support drops, and&amp;hellip;
nothing changes. I have devices that work, I don&amp;rsquo;t need the cloud, and unless
Amazon actively moves to remove the local Wemo control (possible, but unlikely),
my life literally does not change. Except&amp;hellip;within two weeks of that announcement,
I have not one, but &lt;em&gt;two&lt;/em&gt; Belkin Wemo light switches fail. Coincidence? I think
not! (just kidding, I&amp;rsquo;m sure it&amp;rsquo;s a coincidence&amp;hellip;but wow is that timing suspicious).&lt;/p&gt;
&lt;h2 id=&#34;time-to-migrate&#34;&gt;Time to migrate&lt;/h2&gt;
&lt;p&gt;So&amp;hellip;I have two switches to replace. I happily go to CloudFree (actually&amp;hellip;I
got replacements from Amazon). I did some research and apparently products from
Martin Gerry have Tasmota pre-flashed, so I got &lt;a href=&#34;https://www.amazon.com/dp/B0DP6NRLB8&#34;&gt;two of them&lt;/a&gt;.
Mostly this had to do with&amp;hellip;well, I didn&amp;rsquo;t have a working light switch, so
I wanted it kind of&amp;hellip;now.&lt;/p&gt;
&lt;p&gt;I installed the light switches, gave them the same name, enabled MQTT (zero-configuration
for Home Assistant), Belkin Wemo support, and was up and running. With everything
except Alexa. Alexa saw the devices but considered them offline.&lt;/p&gt;
&lt;h2 id=&#34;investigations&#34;&gt;Investigations&lt;/h2&gt;
&lt;p&gt;Home assistant was fine. Operating the light switch was fine. Alexa saw them&amp;hellip;fine.
But absolutely, and still to this day, refuse to operate them. After an inordinate
amount of time, I&amp;rsquo;ve learned why. While the IP address was the same, the name
was the same, everything was the same, there was one difference. In newer
Tasmota firmware, the Belkin support is on the same port as the web interface.
And Alexa stubbornly only likes to connect on (its original port? or does it have
to be 49152-49154?). So, either I move the literal switch web interface to that
port, or Alexa will refuse to operate it. At this point, I&amp;rsquo;m fully in both nerd-snipe
and stubborn mode, and I&amp;rsquo;ll be damned that Alexa will tell me what I can or should
do with my devices.&lt;/p&gt;
&lt;p&gt;A bit of technical background here&amp;hellip;Alexa has always been a bit finicky with home
devices. When you ask Alexa to discover devices, it sends out a broadcast message
for UPnP discovery and listens for responses. In those responses, the Wemos provide
an XML response with a lot of details, including the URL with the port they
operate on. Because this is a broadcast message, with a larger wifi network and
a lot of devices, not all the device data is returned all the time, and that&amp;rsquo;s&amp;hellip;
a bit of a mess for Alexa. However, as far as I can tell, either Alexa a) ignores
at least the port portion of the url returned, or b) forever remembers the port
in your account settings somewhere, even after deleting the device and re-adding
it. Neither of which are ok to me.&lt;/p&gt;
&lt;p&gt;Having some time, I decided to solve the problem with the nuclear option&amp;hellip;&lt;/p&gt;
&lt;h2 id=&#34;replacing-alexa-for-device-control&#34;&gt;Replacing Alexa for device control&lt;/h2&gt;
&lt;p&gt;This was a good personal/work project, as I am always looking for projects to
exercise generative AI for software development to help keep grounded in truth
on the state of the art, since I advise a lot people on this topic.&lt;/p&gt;
&lt;p&gt;So, I pulled out my &lt;a href=&#34;https://kiro.dev&#34;&gt;coding assistant du jour&lt;/a&gt;, and I worked
with the agent to create a specification. I planned to handle this in 2 parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Develop a speech to text service that would listen to the microphone and
get words&lt;/li&gt;
&lt;li&gt;Create a program to interpret the words, and when a device command was
provided, well, it would command the device&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don&amp;rsquo;t know anything about either audio processing, speech to text, or natural
language processing for parts of speech, but luckily I had the help of
various libraries and the collective knowledge of humankind wrapped in an LLM.&lt;/p&gt;
&lt;h2 id=&#34;step-1-speech-to-text&#34;&gt;Step 1: Speech to Text&lt;/h2&gt;
&lt;p&gt;Most modern suggestions will suggest the use of &lt;a href=&#34;https://openai.com/index/whisper/&#34;&gt;Whisper&lt;/a&gt;
for speech to text, but my needs are simple, English only common words, and as
such does not require that much complexity (or computing power). But speech
to text is a problem that&amp;rsquo;s been worked on and solutions existed as far back
as &lt;a href=&#34;https://en.wikipedia.org/wiki/Dragon_NaturallySpeaking&#34;&gt;1997&lt;/a&gt; and probably
earlier. Ultimately, I found &lt;a href=&#34;https://alphacephei.com/vosk/&#34;&gt;Vosk&lt;/a&gt;, which has
several models suitable for English, and used the 40M small model. Because I
like &lt;a href=&#34;https://ziglang.org&#34;&gt;Zig&lt;/a&gt;, I instructed the agent to use that programming
language and pull in the necessary libraries, which included ALSA and Vosk.&lt;/p&gt;
&lt;p&gt;Any time I run into incorporation of shared libraries, I struggle using AI
assistance, and this experience was similar. Ultimately I was able to convince
the AI to create a &lt;a href=&#34;https://git.lerch.org/lobo/aycb-alsa-lib&#34;&gt;zig build of alsa-lib&lt;/a&gt;,
but ultimately failed to do something similar for Vosk, so ended up depending
on the pre-built binaries, both with Kiro and with the Amazon Q Developer CLI,
which at the time was using Claude Sonnet 4. Since then Sonnet 4.5 has come
out, and is a significant step up, but I&amp;rsquo;m still pessimistic. The Vosk build
is somewhat daunting with many dependencies, most of which are pinned versions
of forks of other libraries, and natively uses CMake.&lt;/p&gt;
&lt;p&gt;I spent a fair amount of time cleaning up, but the AI definitely helped out
before I understood audio processing (I have learned a lot but am still a bit
shaky on the topic). At the end of the day, my biggest disappointment is
that I could not optimize this to work on the Raspberry Pi 3B+. I&amp;rsquo;m confident
a Pi 4 would work, but I only had a compute module on hand, and the microphone
wouldn&amp;rsquo;t work with it due to USB support issues (and I have a weird carrier board).&lt;/p&gt;
&lt;p&gt;Because of this I was faced with purchasing a Raspberry Pi, but ultimately
&lt;a href=&#34;https://www.amazon.com/dp/B0F1CL4LTW&#34;&gt;this fanless N150&lt;/a&gt; is about the same
cost, maybe slightly more, after fully kitting out the Pi. I suspect CPU
performance is only slightly more (well done, Pi!), but the I/O performance
will be significantly higher on the N150. At the end of the day, this part of
the process consumes 2-5% CPU and about 450MB RSS, so a Pi 4 should be
perfectly fine keeping up. The code lives here, and the program simply executes
a child process to hand off the text, so it&amp;rsquo;s pretty modular:
&lt;a href=&#34;https://git.lerch.org/lobo/stt&#34;&gt;https://git.lerch.org/lobo/stt&lt;/a&gt;. I&amp;rsquo;ll also note that there are non-English
models for Vosk, so while it is not completely generic, it is far more useful
than my own &amp;ldquo;English only&amp;rdquo; requirement. The code is ok&amp;hellip;I did a fair amount of
cleanup work from the AI, but then used AI again later in various forms, which
added functionality and some more stuff to clean up. In general, my pattern for
these things is to progressively take on more and more of the coding from the
AI. Here, that happened, but I let AI take on some larger tasks toward the end
as I learned more about audio processing through the issues I had with the Pi
3.&lt;/p&gt;
&lt;h2 id=&#34;step-2-understanding-the-text&#34;&gt;Step 2: Understanding the text&lt;/h2&gt;
&lt;p&gt;Step 1 might have had its complexities, but understanding the text is quite a bit
more complicated. Parsing the sentence was not actually the difficulty here, the
problem turned out to be learning through experience how words would be misheard
by the Vosk recognizer. A phrase like &amp;ldquo;turn off the bedroom like&amp;rdquo; doesn&amp;rsquo;t make
any sense and will parse, but parse badly. So you need to look for these common
incorrect words and replace them, which itself can be a bit tricky. The amount
of tuning for both the replacement algorithm and the words themselves took maybe
15% of the overall project.&lt;/p&gt;
&lt;p&gt;Again, AI helped with a lot of the stuff I didn&amp;rsquo;t want to do. Zig&amp;rsquo;s current
TLS implementation is not yet complete, for instance, so I needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A custom download step using curl&lt;/li&gt;
&lt;li&gt;A custom program to tweak some of the downloaded code&lt;/li&gt;
&lt;li&gt;Get all this integrated into the zig build (build.zig)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At AWS we throw around the phrase &amp;ldquo;undifferentiated heavy lifting&amp;rdquo; a lot. The
download step is purely throw away code (once zig TLS is complete), and only
exercised during a build. It&amp;rsquo;s necessary, but my idea of a quality bar on this
particular code is pretty much in the &amp;ldquo;does it work consistently? yeah? Then I
couldn&amp;rsquo;t care less what it looks like&amp;rdquo; space. I ended up needing this on both
of these projects so I did clean it up a little bit, but otherwise, I just
let the AI &amp;ldquo;make it work&amp;rdquo;. The &amp;ldquo;sed lite&amp;rdquo; that I had the AI write was kind of
the same idea. It was a one-off as I&amp;rsquo;d like to pretend this might actually build
on a Windows host (which has &amp;ldquo;curl&amp;rdquo; available enough to do the download), but
Windows to my knowledge does not have any kind of sed, real or pretend.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.link.cs.cmu.edu/&#34;&gt;Link&lt;/a&gt; was my answer to sentence &amp;ldquo;part of speech&amp;rdquo;
processing, and the code was very&amp;hellip;researchy, but it worked, and it was damn
easy to compile. Nothing like a &lt;code&gt;Makefile&lt;/code&gt; that compiles a bunch of c files to
objects, then link the objects into a library to make your day. Understanding
the data structures and nomenclature they use&amp;hellip;that was a different matter, and
we were in way to niche of a space for the AI to help. But it was able to whip
up a &lt;a href=&#34;https://git.lerch.org/lobo/pos/src/commit/5a38fd65c70e0c022324b6e1d8c4dbd26f48bce2/src/root.zig#L80-L216&#34;&gt;console-based sentence parsing diagram&lt;/a&gt;
on the first try, which helped the debugging process a ton. Again, this was
just for debugging&amp;hellip;so pretty much &amp;ldquo;does it work?&amp;rdquo; was my quality bar. It&amp;rsquo;s a
big chunk of code I&amp;rsquo;ve not really looked at. On the other hand, I completely
wrote the word removal/replacement function as I experimented with different
versions of the phrases I&amp;rsquo;d like it to process. AFTER writing it, the AI got
hold of it, refactored it to my direction, and I used a copious amount of
&lt;code&gt;git add&lt;/code&gt; to provide myself checkpoints if it trashed the code.&lt;/p&gt;
&lt;p&gt;AI also did a pretty good job with sending of the commands to the Wemo device,
and I love AI for &amp;ldquo;here&amp;rsquo;s some random protocol I may (or may not) know, but I
know you do. Just go write a function to use that protocol&amp;rdquo;. My experience is
that it usually gets it right, or really close, on the first try. And since
this didn&amp;rsquo;t need to be hooked up to a live microphone, it was pretty easy to
add a bunch of unit tests for various scenarios. Zig&amp;rsquo;s &lt;code&gt;builtin.is_test&lt;/code&gt; even
let me test that commands would be sent properly without actually sending
commands out on the network. Interestingly, AI was unable to reason through
that particular piece, but was able to run with the idea once I coded it.&lt;/p&gt;
&lt;h2 id=&#34;shipping&#34;&gt;Shipping&lt;/h2&gt;
&lt;p&gt;The process for getting this out the door was pretty mundane. Compile the code
for a baseline x86 cpu in release mode, copy both the speech to text and parts
of speech (pos) binaries out, create a service user on the device and a systemd
unit. Nothing fancy. I purchased a cheap USB microphone and tested it, learning
a lot more about audio processing in the meantime (e.g. some mics report 1
channel, some report 2, and they have specific frequencies they can work at, so
you might need to down sample). I did a fair amount of tuning while my wife was
on a business trip, because this needs to be fairly reliable before she starts
using it. There were some problematic deadlocks, failure handling, etc, but one
major change I did was to switch from continuous speech detection to only
process audio once noise is detected. This was a major learning for me (audio
processing noob), and something that came up in an AI chat. It was nice to have
AI make this kind of major change to the codebase, and it continues to be a
pleasant AI experience. Often, significant refactorings&amp;hellip;well, the effort
doesn&amp;rsquo;t seem worth it, or we&amp;rsquo;re too lazy or busy with other things, but I&amp;rsquo;ve
found it&amp;rsquo;s much more likely for me to be willing to move, rename, and redesign
(beyond what refactoring tools can do) a load bearing central component of code
with 1000 references if I can tell AI to go do it while I work on something
else, knowing it won&amp;rsquo;t stop until the build (and its tests) successfully
complete. I&amp;rsquo;m in cleanup mode for sure, but it will have done 90% of that work
for me. Ultimately, a week or so in, I was left with a couple things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Continued need for tuning all the ways it can mis-hear speech (this might
be where Whisper does better?)&lt;/li&gt;
&lt;li&gt;I think there&amp;rsquo;s a slow memory leak, which I ignored in favor of letting
systemd just restart once/day&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I haven&amp;rsquo;t touched the thing in 3 months&amp;hellip;it just&amp;hellip;works. It&amp;rsquo;s not generic
like Alexa, and the speech still isn&amp;rsquo;t quite as robust, but it works!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How I work</title>
      <link>https://emil.lerch.org/how-i-work/</link>
      <pubDate>Thu, 26 Jun 2025 16:58:11 -0700</pubDate>
      
      <guid>https://emil.lerch.org/how-i-work/</guid>
      <description>&lt;h1 id=&#34;how-i-work&#34;&gt;How I work&lt;/h1&gt;
&lt;p&gt;I had someone ask me a while ago &amp;ldquo;how do you work, exactly&amp;rdquo;, because my setup
was strange and new to them. It&amp;rsquo;s actually an interesting question, and it&amp;rsquo;s
evolved over more time than I&amp;rsquo;d care to admit. Because it evolves, this post is
a snapshot in time, representing my setup as of mid 2025. I&amp;rsquo;ll start with an
overview, before getting into the nitty gritty details.&lt;/p&gt;
&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;
&lt;p&gt;At work, I use a Windows virtual machine, using technology from &lt;a href=&#34;https://aws.amazon.com/workspaces-family/workspaces/&#34;&gt;Amazon Workspaces&lt;/a&gt;. This is a special Amazon
version we call &amp;ldquo;corporate workspaces&amp;rdquo;, and gives me the freedom to run whatever
I need to as a client. I originally embraced it due to conflicts between
&lt;a href=&#34;https://www.docker.com/&#34;&gt;Docker&lt;/a&gt; and the Amazon VPN, but I found it useful to
use a desktop at home, and laptop when traveling.&lt;/p&gt;
&lt;p&gt;At home, I have a primary desktop I use, and a couple secondary desktops that
are in use by other members of my family. One of these runs Windows for my son
to play some games, and the others all run Linux. I use the Windows desktop for
games sometimes, though I&amp;rsquo;m starting to play games on Linux. My work laptop is
a Mac that I use primarily for customer demos, testing, and I bring it with me
occasionally traveling for the same reasons.&lt;/p&gt;
&lt;p&gt;The rest of this post will primarily focus on Linux, although most of what I
write will also apply to Mac. Theoretically my setup will work with other
operating systems such as FreeBSD, but I haven&amp;rsquo;t tested this, and my bootstrap
script (more later), would definitely need changes. Philosophically, I try to
stick to as much open source tooling as possible, and I value the ability to
quickly get to a familiar environment, and update to my latest set of tools,
with no surprises. Sticking to a combination of scripting and open source tools
help immensely. However, for work, this isn&amp;rsquo;t possible and as a result, I don&amp;rsquo;t
even try. Avoiding Outlook for instance is possible at Amazon, but it is too
much work, and I couldn&amp;rsquo;t realistically function without Word and PowerPoint in
my role. My corporate workspace serves as a good partition, however, so I lean
on that. To have maximum portability/flexibility, I want to be able to have the
same set of tools everywhere. Sometimes that means over ssh, so over time I&amp;rsquo;ve
become focused on CLI/TUI workflows.&lt;/p&gt;
&lt;p&gt;With this in mind, my general approach to using software is the following
hierarchy, rated in order, so I will look for software starting at the top
of this list, and traverse down, stopping as soon as a suitable option is
available:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open source CLI&lt;/li&gt;
&lt;li&gt;Open source TUI&lt;/li&gt;
&lt;li&gt;Open source GUI&lt;/li&gt;
&lt;li&gt;Proprietary CLI&lt;/li&gt;
&lt;li&gt;Proprietary TUI&lt;/li&gt;
&lt;li&gt;Proprietary GUI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because proprietary software is&amp;hellip;proprietary, I trust it less, especially in
recent years as telemetry has gotten commonplace, and I just don&amp;rsquo;t feel like
people should be recording my every move. Also, I can&amp;rsquo;t tell what else the
software might be doing, and while Android and iOS have deep permission structures
with user consent, no such system is built into desktop operating systems. So,
with very few exceptions, I sandbox proprietary software in one of the following
ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.flatpak.org/&#34;&gt;Flatpak&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://distrobox.it/&#34;&gt;Distrobox&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this time of this writing, I &lt;strong&gt;believe&lt;/strong&gt; the only exception to the Flatpak/
Distrobox sandboxing is NVidia GPU drivers. And the only distrobox usage at this
time is for Amazon Workspaces. However&amp;hellip;I do have one piece of Amazon software
(an unreleased product), installed through more native means. This is not
open source, but as an employee I actually do have access to the source code,
so it&amp;rsquo;s mostly aligned with my philosophy.&lt;/p&gt;
&lt;p&gt;For open source software, I will install CLI/TUI tools through &lt;a href=&#34;https://nixos.org/&#34;&gt;nix&lt;/a&gt;,
and GUI tools through the following means, usually in this order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nix&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://appimage.org/&#34;&gt;AppImage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Flatpak&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I do not generally script most of my base OS installations, but I have &lt;a href=&#34;https://emil.lerch.org/bs.sh&#34;&gt;scripted
out base utilities&lt;/a&gt; to manage the rest of my setup.
This includes my home directory and configuration, including nix configuration
(but not the nix utility install). Using a random linux machine with internet
access, I can be productive with most of my tools after downloading and running
my bootstrap script. To get all the CLI/TUI tools I&amp;rsquo;m used to using, I can install nix
and type &lt;code&gt;nix-configure&lt;/code&gt;, and any updates in my configuration are handled by
&lt;code&gt;mr up &amp;amp;&amp;amp; nix-configure&lt;/code&gt;. AppImages are installed via nix, and flatpak&amp;hellip;I just
have a text file with common stuff. Workspaces is a manual process for the moment.&lt;/p&gt;
&lt;p&gt;Nix focuses on determinism, so by centering as much on nix as possible, I
minimize the amount of surprises in my life. Moving from machine to machine, as
long as my configuration repositories are at the same commit, all my tools
will be at the same version, and I&amp;rsquo;ll have all the same bugs/features in my tools
everywhere. For operating systems, I stick to Debian where possible, and install
as little on top of the base system as possible. The last time I rebuilt,
I captured a text file with the apt packages I install - it&amp;rsquo;s about 26 items
to handle local hardware and a base X Windows configuration (I have not switched
to Wayland). However, my laptop runs &lt;a href=&#34;https://system76.com/pop/&#34;&gt;PopOS!&lt;/a&gt; which
is much more maximalist. I don&amp;rsquo;t really care, because nix-installed tooling
takes precedence over whatever was installed out of the box.&lt;/p&gt;
&lt;h2 id=&#34;how-does-this-work-part-1-bootstrap-and-configuration&#34;&gt;How does this work? Part 1: Bootstrap and configuration&lt;/h2&gt;
&lt;p&gt;Starting from the beginning, my bootstrap script, which hasn&amp;rsquo;t changed since
2021, installs some important base utilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://source.myrepos.branchable.com/?p=source.git;a=blob_plain;f=mr;hb=HEAD&#34;&gt;mr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://raw.githubusercontent.com/RichiH/vcsh/66944d009b8df64e0b2ddae757a83899ff8684b7/vcsh&#34;&gt;vcsh, pinned to a prior commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;git&lt;/li&gt;
&lt;li&gt;Other dependencies, if necessary&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The other dependencies include curl (to download mr/vcsh), and perl (which mr uses).
vcsh, at the pinned commit, is a set of shell scripts. Beyond that commit it has
changed architecture. These two tools are critical to all my configuration, and
roughly follows the pattern outlined in the &lt;a href=&#34;https://germano.dev/dotfiles/#mr&#34;&gt;advanced configuration of this
blog&lt;/a&gt;. tl;dr, vcsh allows me to manage
multiple git repositories in the same directory (here, &lt;code&gt;$HOME&lt;/code&gt;), and mr
allows me to quickly manage multiple repositories in that directory. While I
end up with a lot of repositories (mr manages 39 repos at the time of writing),
I like the fact that I have a configuration repository for each tool I use,
rather than a single &lt;code&gt;dotfiles&lt;/code&gt; repo as many recent tools &lt;a href=&#34;https://www.chezmoi.io/quick-start/#start-using-chezmoi-on-your-current-machine&#34;&gt;guide you to
using&lt;/a&gt;.
I&amp;rsquo;m likely swimming upstream a bit here, but I am mostly happy with my setup.
My repos on github all have the prefix &lt;code&gt;vcsh_&lt;/code&gt;, which was a mistake, and
&lt;a href=&#34;https://burkeholland.github.io/posts/codespaces-dotfiles&#34;&gt;GitHub has special treatment for a &lt;code&gt;dotfiles&lt;/code&gt; repo&lt;/a&gt;,
which is not incompatible with my setup, but I have not configured this as I
am not a huge codespaces user. I actually like the concept of codespaces, but
I always have my tools at my disposal. ;)&lt;/p&gt;
&lt;p&gt;So&amp;hellip;what are in these 39 repos? There are a few raw repos outside vcsh for things
like liquidprompt and zsh autosuggestions, but primarily these are configuration
files. Right now, I have 32 configuration repositories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Xmodmap&lt;/li&gt;
&lt;li&gt;Xresources&lt;/li&gt;
&lt;li&gt;alacritty&lt;/li&gt;
&lt;li&gt;aliases&lt;/li&gt;
&lt;li&gt;asoundrc&lt;/li&gt;
&lt;li&gt;awsaliases&lt;/li&gt;
&lt;li&gt;bash_profile&lt;/li&gt;
&lt;li&gt;bash_prompt&lt;/li&gt;
&lt;li&gt;bashrc&lt;/li&gt;
&lt;li&gt;commonrc&lt;/li&gt;
&lt;li&gt;desktop&lt;/li&gt;
&lt;li&gt;dunst&lt;/li&gt;
&lt;li&gt;exports&lt;/li&gt;
&lt;li&gt;fehbg&lt;/li&gt;
&lt;li&gt;functions&lt;/li&gt;
&lt;li&gt;ghostty&lt;/li&gt;
&lt;li&gt;gitconfig&lt;/li&gt;
&lt;li&gt;gtkrc-2.0&lt;/li&gt;
&lt;li&gt;i3config&lt;/li&gt;
&lt;li&gt;i3status&lt;/li&gt;
&lt;li&gt;liquidpromptrc&lt;/li&gt;
&lt;li&gt;mdlrc&lt;/li&gt;
&lt;li&gt;mlterm&lt;/li&gt;
&lt;li&gt;mr&lt;/li&gt;
&lt;li&gt;muttrc&lt;/li&gt;
&lt;li&gt;nix&lt;/li&gt;
&lt;li&gt;tmux.conf&lt;/li&gt;
&lt;li&gt;vim&lt;/li&gt;
&lt;li&gt;xinitrc&lt;/li&gt;
&lt;li&gt;xprofile&lt;/li&gt;
&lt;li&gt;zellij&lt;/li&gt;
&lt;li&gt;zshrc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Many of these I haven&amp;rsquo;t touched for years. Several of them are for programs
I don&amp;rsquo;t currently use (e.g. Alacritty). But they&amp;rsquo;re all small and self-contained,
and if I cared, I could configure this out of mr easily enough. My bootstrap
clones the myrepos repo, then myrepos can do the heavy lifting from there.&lt;/p&gt;
&lt;h2 id=&#34;how-does-this-work-part-2-working-with-shells&#34;&gt;How does this work? Part 2: Working with shells&lt;/h2&gt;
&lt;p&gt;mr manages my shell configuration. I prefer
&lt;a href=&#34;https://en.wikipedia.org/wiki/Z_shell&#34;&gt;zsh&lt;/a&gt;, though I don&amp;rsquo;t actually use many
of its special features. I do love the history sharing and a few other
niceties. However, zsh isn&amp;rsquo;t usually the default when I&amp;rsquo;m encountering a new
machine, and I want to get from 0-&amp;gt;productive as quickly as possible, so it&amp;rsquo;s
important to me that bash work just fine. Today, bash and zsh look and feel
nearly identical to me.&lt;/p&gt;
&lt;p&gt;The way this works is through the use of a common file that works for both
bash and zsh. Both &lt;code&gt;.bashrc&lt;/code&gt; and &lt;code&gt;.zshrc&lt;/code&gt; source this common &lt;code&gt;.commonrc&lt;/code&gt; file,
and only commands that are specific to one shell or the other get put into &lt;code&gt;.bashrc&lt;/code&gt;
and &lt;code&gt;.zshrc&lt;/code&gt;. A nice effect of this is that when some utility I&amp;rsquo;m experimenting
with decides to spam my login scripts, it&amp;rsquo;s another quick indication that
my files have been trashed. Right now, my bashrc is only 16 lines long, and
my zshrc is 41 lines, with nearly all my logic in commonrc. My other indication
is that all these files are in source control, so I can tell by vcsh that something
has been touched.&lt;/p&gt;
&lt;p&gt;To handle local customization to my shells that I don&amp;rsquo;t want propagated, my
&lt;code&gt;.commonrc&lt;/code&gt; file ends up looking for and sourcing &lt;code&gt;.extra&lt;/code&gt; and &lt;code&gt;.extraextra&lt;/code&gt;
if they exist. These do not go into source control. The reason for two files here
has to do with &lt;code&gt;$PATH&lt;/code&gt;. The first &lt;code&gt;.extra&lt;/code&gt; file is designed to be the last thing
to mess with that variable, then &lt;code&gt;.extraextra&lt;/code&gt; can be whatever else is necessary.&lt;/p&gt;
&lt;p&gt;The rest is a bit boring as the script sets preferences, always following a
&amp;ldquo;if this tool is installed, prefer it, else look for the next tool&amp;rdquo; approach.
So things always work, but after I install a bunch of my favorite tools, I&amp;rsquo;ll
get more and more power. &lt;code&gt;nix-configure&lt;/code&gt; gets nearly all my favorite tools installed,
but nix is not worth bothering with on disposable cloud instances, for example,
so sometimes I&amp;rsquo;ll &lt;code&gt;apt install&lt;/code&gt; one or two tools I want, and let my rc scripts
get them preferred. Basically, this is &lt;a href=&#34;https://en.wikipedia.org/wiki/Progressive_enhancement&#34;&gt;progressive
enhancement&lt;/a&gt; adapted to
the command line.&lt;/p&gt;
&lt;p&gt;The last thing of note here is that there is a great set of zsh functions/helpers/
plugins/themes called &lt;a href=&#34;https://ohmyz.sh/&#34;&gt;oh my zsh&lt;/a&gt; that my rc scripts use.
However, I found that oh my zsh almost works in bash as well. I have refactored
this so it &lt;a href=&#34;https://github.com/elerch/oh-my-bash-zsh&#34;&gt;works on both shells&lt;/a&gt;, and
use my repository instead.&lt;/p&gt;
&lt;h2 id=&#34;how-does-this-work-part-3-getting-my-normal-programs-onboard&#34;&gt;How does this work? Part 3: Getting my normal programs onboard&lt;/h2&gt;
&lt;p&gt;So far, I&amp;rsquo;ve covered basic bootstrap through mr. This gets me access to any
&amp;ldquo;unixy&amp;rdquo; terminal on Mac, Linux, BSDs, and even &lt;a href=&#34;https://termux.dev/en/&#34;&gt;Termux&lt;/a&gt;
on Android works after a few &lt;code&gt;pkg install&lt;/code&gt; commands. I haven&amp;rsquo;t tried but it
would likely work on &lt;a href=&#34;https://cygwin.com/&#34;&gt;Cygwin in Windows&lt;/a&gt; as well. This
works well because everything is text files and shell scripts, at least up
until now.&lt;/p&gt;
&lt;p&gt;So&amp;hellip;where do binaries come in? Today, this is through Nix. Before I extol the
virtues of Nix, I first want to discuss my severe discomfort with the tool:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is an OS (NixOS), a package manager, and a programming language.
This causes a ton of confusion&lt;/li&gt;
&lt;li&gt;The sands shift&amp;hellip;a lot. I am not a heavy user, and generally when I ask
a search engine or AI how to do something, probably 90% of the time, the nix
CLI will tell me I&amp;rsquo;m using a deprecated way of doing it. I love that nix
keeps old versions of its reference manuals around, but often times the first
search result will take me to &amp;ldquo;the documentation&amp;rdquo;, but it&amp;rsquo;s an older version
of the docs that again&amp;hellip;leads me to deprecated ways of doing things.&lt;/li&gt;
&lt;li&gt;Corollary to the above&amp;hellip;there are two ways of doing things today between
normal things and Nix Flakes. Flakes are super-useful, especially doing
development. It&amp;rsquo;s &lt;a href=&#34;https://determinate.systems/posts/experimental-does-not-mean-unstable/&#34;&gt;stable&lt;/a&gt;,
but requires one to add &amp;ldquo;experimental features&amp;rdquo; configuration to use.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s not kind to your machine. Nix package search is unusable without modern
equipment, and I&amp;rsquo;ve had it consume all the memory on my low end equipment.
Today, I consider nix search unusable (nix-search-cli works well, but requires
Internet access). The nix store loves storage space as much as nix native
search loves RAM, and it&amp;rsquo;s super-easy to download the Internet and thrash
your CPU when you install new packages (more on this later).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With that out of the way, why do I use nix (the package manager&amp;hellip;and as little
of the programming language as possible)?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All the machines I use, I know I get the same version of the software everywhere&lt;/li&gt;
&lt;li&gt;I have really good confidence everything will work&lt;/li&gt;
&lt;li&gt;Development environments can use nix, and I really like this workflow&lt;/li&gt;
&lt;li&gt;It handles much of the &amp;ldquo;find the right binaries for this system and wire it up&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This said, I do one &amp;ldquo;unnatural thing&amp;rdquo; with nix. Because NixOS doesn&amp;rsquo;t really
like the way AppImages work, they instead &lt;a href=&#34;https://nixos.org/manual/nixpkgs/stable/#sec-pkgs-appimageTools&#34;&gt;extract and wrap AppImage files
&lt;/a&gt; with a fairly
involved process. I don&amp;rsquo;t really have time for that, I don&amp;rsquo;t use NixOS, and
don&amp;rsquo;t really care to go through whatever work it would take to play nice. The
AppImages are all controlled by hash anyway, so they&amp;rsquo;re still deterministic. With
that in mind, I have a few flakes that &lt;a href=&#34;https://git.lerch.org/lobo/chawan-flake/src/commit/974100f81d4377a9265d3ab6bf0f3d2c4b4f7e4d/flake.nix&#34;&gt;avoid the wrapper, and just slap the
AppImage into place&lt;/a&gt;.
I&amp;rsquo;m sure purists hate this&amp;hellip;but I&amp;rsquo;m a bit more pragmatic, and it works for me.&lt;/p&gt;
&lt;p&gt;So&amp;hellip;if I am going beyond &amp;ldquo;just get my shell configured in my way&amp;rdquo; on a machine,
my next steps after bootstrap + &lt;code&gt;mr up&lt;/code&gt; is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install nix (I use single-user, but I don&amp;rsquo;t think it matters)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;nix-configure&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;nix-configure&lt;/code&gt; is a script that lands in &lt;code&gt;~/.local/bin&lt;/code&gt;, and by default installs
all the things, subject to &amp;ldquo;I don&amp;rsquo;t want monstrosities copied down from the
internet&amp;rdquo;. There is a command line option on the script to say, &amp;ldquo;no, really,
download the Internet please&amp;rdquo;, and that will get all the things that require
X Windows, etc. The difference between light and heavy is purely subjective.
I&amp;rsquo;ve also recently learned about nix builders and substituters (cache), and have
begun configuring them in. This, I hope, will help substantially with low-powered
machines like setting up and using a Raspberry Pi. Once I feel more comfortable
with this setup, it probably warrants a whole different blog post.&lt;/p&gt;
&lt;p&gt;This works great for 95% of what I need to do. One sore spot for me is vim
(actually, &lt;a href=&#34;https://neovim.io/&#34;&gt;neovim&lt;/a&gt;). The neovim plugins I use always seem
to throw various errors, because they&amp;rsquo;re not also locked into my system. This
is completely doable, but I have not gone through the effort of solving it.
Usually, I put up with the errors for a while, then get frustrated and try to
figure out what broke. Usually, it&amp;rsquo;s a new change in a plugin that I haven&amp;rsquo;t
bothered to update the corresponding config. Many times, it&amp;rsquo;s some combination
of the following vim commands that fix the problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;:PlugInstall&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:PlugUpdate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:COQdeps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;:TSUpdate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Lastly, waiting a while while nvim-treesitter trashes your machine
compiling a lot of language files&lt;/li&gt;
&lt;li&gt;Occasionally, &lt;code&gt;:checkhealth&lt;/code&gt; tells me something useful&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Treesitter is awesome, but walking up to new machines can be painful. It also
is usually the thing that messes with me, and usually I end up finding some
random breaking change they made in a GitHub issue or discussion, etc. For
instance, just now prepping this post, I learned in the last two months, treesitter
team decided to change the primary branch (but it&amp;rsquo;s not yet the default on
GitHub), and changed the name of the configuration file that you need to
require in lua. Both of these seem fairly arbitrary breaking changes, and
both of which need detective work to track down why, once again, everything
is just broken.&lt;/p&gt;
&lt;h2 id=&#34;how-does-this-work-part-4-software-development&#34;&gt;How does this work? Part 4: Software development&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;ve read this far, and you have looked at &lt;a href=&#34;https://github.com/elerch/vcsh_nix/blob/master/.nix-flake/flake.nix&#34;&gt;my nix configuration
repo&lt;/a&gt;, you
may have noticed the complete lack of any programming languages being installed.
I work in &lt;a href=&#34;https://ziglang.org/&#34;&gt;Zig&lt;/a&gt; a lot, and from time to time, I work
with go, Java, Python, Rust, Node, or a host of other things. So&amp;hellip;how does
this work?&lt;/p&gt;
&lt;p&gt;Enter &lt;a href=&#34;https://mise.jdx.dev/&#34;&gt;mise&lt;/a&gt;. This is my newest addition, but it replaces
a similar tool called direnv. They both do the same thing, but I think mise
does it better. When I change directories, if mise sees a configuration file
in that directory, it will adjust the paths, set up environment variables, or
run Nix flakes (though that&amp;rsquo;s a little unnatural at the moment) to get everything
set up the way that I need them for that project. Direnv just installed the tools,
but with mise, I have it configured to just warn me if a tool isn&amp;rsquo;t installed,
so I don&amp;rsquo;t have surprises if I&amp;rsquo;m just poking around, and I can choose to install
with &lt;code&gt;mise install&lt;/code&gt; (this just looks at the configuration and installs/uses
the correct tools for the directory I&amp;rsquo;m in.&lt;/p&gt;
&lt;p&gt;This works mostly great. One exception, is that I use vi for development, so if
for some reason, I would rather use a graphical editor (usually something like
VS Code, out of a Flatpak sandbox), well, this is a whole different problem.
Flatpak has &amp;ldquo;SDKs&amp;rdquo; that can be installed and manually configured, but right now,
it&amp;rsquo;s kind of a pain and it&amp;rsquo;s also too niche for me since I usually am at the
command line using neovim. So I ignore it.&lt;/p&gt;
&lt;h2 id=&#34;overall&#34;&gt;Overall&lt;/h2&gt;
&lt;p&gt;Is this perfect? Not by a long shot.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Updates sometimes require &lt;code&gt;mr up&lt;/code&gt; (to update mr repo itself) followed by &lt;code&gt;mr up&lt;/code&gt; (to update the vcsh repos)&lt;/li&gt;
&lt;li&gt;Bouncing around machines often mucks with config, which then have conflicts I have to resolve on &lt;code&gt;mr up&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;I need a recent (within the last 4 years or so) version of git installed by the
bootstrap process due to my &lt;code&gt;.gitconfig&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Vim plugins (well, let&amp;rsquo;s be honest, 99% just treesitter) are a hot mess that I
don&amp;rsquo;t have under control&lt;/li&gt;
&lt;li&gt;AppImage considerations mentioned above&lt;/li&gt;
&lt;li&gt;Nix still has a lot of downsides, and I haven&amp;rsquo;t even touched on the pain that
I find Nix, the programming language&lt;/li&gt;
&lt;li&gt;Mise + nix flakes are still emerging&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But when it works, and actually, it works most of the time, it is pretty amazing.
All my configuration and tools are there, ready for me, and I can be productive
in less than a minute.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Knack backpack</title>
      <link>https://emil.lerch.org/knack-backpack/</link>
      <pubDate>Sun, 23 Mar 2025 11:32:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/knack-backpack/</guid>
      <description>&lt;h1 id=&#34;knack-series-2-medium-expandable-knack-pack&#34;&gt;Knack Series 2 Medium Expandable Knack Pack&lt;/h1&gt;
&lt;p&gt;I recently got &lt;a href=&#34;https://knackbags.com/products/medium-expandable-backpacks-series-2&#34;&gt;one of these packs&lt;/a&gt;
(limited edition pinecone color, no longer available), and I timed
the order to take advantage of their 30 day trial policy, while giving it a
serious run around the Earth, with travel planned from Portland to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Seattle (overnight)&lt;/li&gt;
&lt;li&gt;Seattle (day trip)&lt;/li&gt;
&lt;li&gt;One week to India&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As the bag is much more expensive than typical bags, I wanted to be really sure
that it is a good bag. My March has has even more travel (add overnight to
Manhattan and Singapore to the list above), so I&amp;rsquo;ve been taking notes while
things are fresh in my mind. Of note, while I travel a lot (120k miles in 2024)
and get great status benefits, my company is ridiculously cheap and my status
does not get me upgrades on foreign carriers for free. My flights back and forth
to India were in regular economy (so my comments below about seat space relate
to economy rather than business or first).&lt;/p&gt;
&lt;p&gt;tl;dr - this is a great bag, and worth the money&lt;/p&gt;
&lt;h2 id=&#34;the-good&#34;&gt;The Good&lt;/h2&gt;
&lt;p&gt;So&amp;hellip;pretty much everything was good. The backpack is clearly very thoughtfully
made, and I&amp;rsquo;m sure I haven&amp;rsquo;t even noticed 100 things I really like about the bag.
Here is a short list of the things that have surprised me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The whole backpack gets used&lt;/strong&gt;: This is a bit weird, but one of my first thoughts
was &amp;ldquo;wow, I&amp;rsquo;m just in and out of this backpack all the freaking time&amp;rdquo;. That sounds
annoying, but I quickly realized, I was actually &lt;strong&gt;using&lt;/strong&gt; the backpack. Every
single thing I put in the backpack was getting used. My old bag was much bigger,
and I had a bunch of stuff in there that just sat, unused, for the &amp;ldquo;just in case&amp;rdquo;
scenario, which never came. Switching backpacks, I pulled stuff I hadn&amp;rsquo;t touched
for a year or more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The backpack is light&lt;/strong&gt;: This is objectively true, but also, because I&amp;rsquo;m not
putting extra stuff in the backpack, approximately half the time I pick up the
bag, I&amp;rsquo;m worried I&amp;rsquo;ve left something behind because it&amp;rsquo;s so damn light.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The backpack strap pocket&lt;/strong&gt;: OMG&amp;hellip;this seemed so frivolous. Who needs, and
who is actually going to use, that backpack strap pocket? Apparently - this guy!
To be fair, it wasn&amp;rsquo;t until the trip to India where I realized its value. Day
trip and overnight (even two nights) I didn&amp;rsquo;t need any other bag, so it was just
me and this bag packed with clothes/toiletries in the luggage area of this bag,
and it was permanently &amp;ldquo;a backpack&amp;rdquo;. When I went to India, it remained so until
after the first leg of my flight, when I had it over the handles of my luggage
and it occurred to me &amp;ldquo;I don&amp;rsquo;t need these straps&amp;hellip;I can tuck them in!&amp;rdquo;. I thought
I was so cool, and it looked nice. Then I got on the plane, and I realized, this
isn&amp;rsquo;t just a gimmick. By tucking them in, sliding the bag under the seat in front
of me was objectively easier - as it just slides underneath. Also, the straps
weren&amp;rsquo;t spilling out into the aisle or my seatmate or the area under me at all.
Afterwards, converting it back to a backpack was super easy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passport pocket&lt;/strong&gt;: I didn&amp;rsquo;t even know this existed until I watched a video
on how to pack the bag. Then I thought it was a bit gimmicky. But once I traveled
internationally, that pocket is in the exact correct place to give access when
you have it looped over luggage and are wheeling through airport security and
customs and immigration and the thousand other times you need to show some
officer or lounge person or hotel employee at checkin your passport.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The main compartment/computer sleeve&lt;/strong&gt;: This was surprisingly large. I was worried about this,
as overall the bag was much smaller than my old backpack. How would I put my
stuff in there? Then I remembered&amp;hellip;my &lt;strong&gt;computer does not go in that compartment&lt;/strong&gt;!
Having the computer in its dedicated place means a) I don&amp;rsquo;t need a sleeve, b)
the bag doesn&amp;rsquo;t have to integrate a sleeve in the main compartment, and c) the
computer is against an area almost guaranteed to be flat. It gave me a lot of
confidence that I can stuff the bag pretty full and not worry about stressing
the computer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Overall size&lt;/strong&gt;: I was really concerned about the size of the bag since I was
ordering over the Internet. Would it always fit under the seat in front of me
on planes? Would my computer fit? Would all my stuff fit? What about it being
expanded? Nothing to worry about&amp;hellip;I didn&amp;rsquo;t even need to expand the bag for my
overnight trip, but later I did expand it just as an experiment. Even expanded,
there was absolutely no problem having it in the seat in front of me. For the
computer size, if I had a giant desktop replacement, this would be an issue,
but my advice here is simply to follow the advice on the web site. My 14&amp;quot; was
perfectly fine.&lt;/p&gt;
&lt;h2 id=&#34;the-bad&#34;&gt;The Bad&lt;/h2&gt;
&lt;p&gt;None of my issues with the bag are minor. I had to think long and hard to come
up with anything at all. This is all nitpicking.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Usage:&lt;/strong&gt; So, a bit embarrassing, but I struggled with &amp;ldquo;where do I put my stuff&amp;rdquo;.
Eventually I watched a video, but I can&amp;rsquo;t seem to find it. It might have been
this: &lt;a href=&#34;https://www.youtube.com/watch?v=44ovrl04tmE&#34;&gt;https://www.youtube.com/watch?v=44ovrl04tmE&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Internal pockets:&lt;/strong&gt; For my usage, the interior pocket layout is not 100% ideal.
However, I can&amp;rsquo;t tell you exactly what I would change generally. Sometimes I feel
like I want less pockets&amp;hellip;sometimes it&amp;rsquo;s a feeling that I want more zippered
pockets. It would be nice to have easier access to some pockets when in a horizontal
configuration, as I do use it slipped over handles quite a bit. This is mostly
non-specific feelings, but I do have 4 specific items though that I can pin down.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The bag has a &amp;ldquo;hidden pocket&amp;rdquo; that is really, &lt;strong&gt;really&lt;/strong&gt; useful for passports.
Transiting through foreign airports, you need access to your passport a lot,
and it&amp;rsquo;s the perfect place and orientation for the job. However, this
pocket should be maybe 1/4&amp;quot; wider (at least). Why? Because with just that much more width,
it would be possible to put a paper boarding pass in there with the passport
and zip it up. Going through international airports, I ended up with paper
boarding passes a lot, and even paper lounge passes of the same size.
I left the pocket unzipped and placed the boarding/lounge passes vertically
so both they and my passport were available to various security checkpoints,
lounge and boarding agents. Being able to put it in horizontally and zip
it occasionally would have been nice.&lt;/li&gt;
&lt;li&gt;I think they call this the &amp;ldquo;sunglasses pocket&amp;rdquo;. This should be a little bigger
(more depth, maybe a touch wider) for my usage. In that pocket, I have a
&amp;ldquo;hockey puck&amp;rdquo; wifi hotspot, earbud headphones, and my work badge. It&amp;rsquo;s a tight
fit.&lt;/li&gt;
&lt;li&gt;The laptop pocket. I struggled with this pocket for a while&amp;hellip;my 14&amp;quot; laptop
fits &lt;strong&gt;just fine&lt;/strong&gt; in the pocket, but I really struggled to get the laptop
in and out for a while until I realized I was doing it wrong. Seriously.
It turns out that my hand was bumping into the attachment point for the
backpack strap, and I was assuming the zipper stopped there, which meant that
the opening I was trying to use is about an inch shorter than what it was
designed for. Once I realized what I was doing, there&amp;rsquo;s plenty of room.
Like I said, these are nits&amp;hellip;I&amp;rsquo;m not sure what they could do about this,
but it was a thing that happened.&lt;/li&gt;
&lt;li&gt;The suitcase area hold down strap. I would like this to be more secure somehow,
like maybe a zippered area around the whole thing similar to the other side.
I suspect that particular suggestion won&amp;rsquo;t work due to expandability needs,
but coming into the bag from the side there is a tendency to ruffle clothes
with the current setup.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Slipping it over suitcase handles:&lt;/strong&gt; I find while this works great, it tends
to &amp;ldquo;hang up&amp;rdquo; a bit about half way down on the mechanism that allows my suitcase
(an ebags Mother Lode roller board &lt;a href=&#34;https://www.ebay.com/itm/127009380018&#34;&gt;similar to this&lt;/a&gt;)
handles to telescope. This could be as much of a problem with my suitcase as the
bag (I have had similar, but less severe problems with other bags), but I add it
here because I think I know what Knack can do to make this excellent bag better.
My suggestion - on the edge of the back of the backpack furthest away from the
side handle (the part that first goes over the handles when slipping it down),
put some hard plastic such that it is the hard plastic that hits the handle
joint rather than the softer edge of the backpack itself. This hard plastic could
be along the entire edge, or maybe it is two pieces of hard plastic that users
can attach and slide around, similar to the way the chest straps are designed.&lt;/p&gt;
&lt;h2 id=&#34;the-still-unknown&#34;&gt;The still unknown&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Headphones + neck pillow&lt;/strong&gt;: This combination proves a bit much for the bag.
First, I tried attaching headphones to the loops at the bottom, but they would
swing around when on my back, which felt weird, and it didn&amp;rsquo;t really work well
when the bag was over the handles either. Then I realized that the ridiculously
large main compartment (that doesn&amp;rsquo;t seem that large, but it is) happily handles
the big over the ear headphones, while in their case, just fine. That left me
with the neck pillow, which I strapped to the carry handle. It worked fine, and
the pillow might have fit inside the main compartment if I hadn&amp;rsquo;t used the
headphone case, or if I had put it into the suitcase area. I just don&amp;rsquo;t know.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Water bottle pocket&lt;/strong&gt;: This is really cool, but I&amp;rsquo;m right handed, and would
like it on the right side of the bag so I can more easily reach back while
I have it on and grab stuff from that pocket. Also, it would be accessible when
slipped over luggage handles. This would be in the bad category, but putting an
engineer hat on for a second, I don&amp;rsquo;t see how it&amp;rsquo;s physically feasible while
maintaining a) integrity of the bag, and b) allowing for the horizontal handle
to remain. The horizontal handle is &lt;strong&gt;way&lt;/strong&gt; more important in general usage.
In my mind, I imagine that inside Knack, there were &lt;a href=&#34;https://www.explainxkcd.com/wiki/index.php/1741:_Work&#34;&gt;4 hours of meetings&lt;/a&gt;
over this very topic. One thing I learned about that pocket&amp;hellip;if you don&amp;rsquo;t
have a water bottle there, it&amp;rsquo;s a great quick place to throw wallet and cell
phone when you&amp;rsquo;re going through security.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It seems to get flagged more by security?&lt;/strong&gt;: However, I&amp;rsquo;m not sure if this is
because I&amp;rsquo;ve been going through all kinds of different airports with varying
security regulations (India literally wanted everything with a wire or
microprocessor out of the bag), or if there&amp;rsquo;s something about the bag. It could
very well be me, or my imagination. Domestically I think it&amp;rsquo;s only been pulled
twice, but one of those times was coming through customs when the rules were
normal (not PreCheck), and I left my computer in the bag.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The little loops at the bottom of the bag&lt;/strong&gt;: I still literally have no idea
what that&amp;rsquo;s for. I tried hanging my headphones off them, but it feels weird when
something is swinging off them as you&amp;rsquo;re walking. I look forward to figuring that
out.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Building Unikraft unikernels the hard way</title>
      <link>https://emil.lerch.org/building-unikraft-unikernels-the-hard-way/</link>
      <pubDate>Tue, 09 Jul 2024 14:14:48 -0700</pubDate>
      
      <guid>https://emil.lerch.org/building-unikraft-unikernels-the-hard-way/</guid>
      <description>&lt;h1 id=&#34;building-unikraft-unikernels-the-hard-way&#34;&gt;Building Unikraft unikernels the hard way&lt;/h1&gt;
&lt;p&gt;You&amp;rsquo;ll note from my last post that unikernels have a number of advantages for
building services, and &lt;a href=&#34;https://www.unikraft.io/&#34;&gt;Unikraft (the company)&lt;/a&gt; is making a run for
a commercial offering based on the &lt;a href=&#34;https://github.com/unikraft/unikraft/&#34;&gt;Unikraft open source microkernel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Of course, they&amp;rsquo;re doing what they should be doing. Make a unikernel that can
run linux binaries without modification. But that is dull and boring, and
introduces a ton of overhead&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;! Can we do better, but ignoring all that, and
embracing the inner workings of the microkernel build system?&lt;/p&gt;
&lt;p&gt;Can we do it with &lt;a href=&#34;https://www.ziglang.org&#34;&gt;Zig&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;Of course we can! Let&amp;rsquo;s go play&lt;/p&gt;
&lt;h2 id=&#34;getting-started&#34;&gt;Getting started&lt;/h2&gt;
&lt;p&gt;First things first&amp;hellip;let&amp;rsquo;s get hello world going. All this is easiest on Linux
or macOS, and if using the cloud you need to make sure you have access to kvm, which may
not be the easiest thing, nor the cheapest. Windows with WSL2 should also work,
but I have only done any of this on Linux. Once you determine where you&amp;rsquo;re going
to try this stuff, you&amp;rsquo;ll want to get &lt;a href=&#34;https://www.qemu.org/download/#linux&#34;&gt;QEMU&lt;/a&gt;
installed, as well as &lt;a href=&#34;https://github.com/unikraft/kraftkit&#34;&gt;kraftkit&lt;/a&gt;. Kraftkit
has a few dependencies&amp;hellip;if I remember correctly, I had most of what I needed
on a default debian distribution except flex, bison and libncurses-dev (needed
for &lt;code&gt;kraft menu&lt;/code&gt; command). From there, you&amp;rsquo;ll want to clone the
&lt;a href=&#34;https://github.com/unikraft/catalog/&#34;&gt;unikraft catalog&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;hello-world&#34;&gt;Hello world!&lt;/h2&gt;
&lt;p&gt;The catalog is set up with a number of examples in the &lt;code&gt;examples/&lt;/code&gt; directory.
&lt;strong&gt;THIS IS NOT WHERE WE WILL PLAY!&lt;/strong&gt;. These examples are the boring, &amp;ldquo;build a
regular Linux application and watch it work&amp;rdquo; stuff. We want the hard mode,
advanced, expert stuff, which is located in the &lt;code&gt;native/&lt;/code&gt; directory. That&amp;rsquo;s where
all the cool kids hang out, and we want to party there.&lt;/p&gt;
&lt;p&gt;In that directory, there is a helloworld-c directory. Go into that directory,
and do a &lt;code&gt;kraft build&lt;/code&gt; and &lt;code&gt;kraft run&lt;/code&gt;. Make sure to choose qemu/x86_64 when asked.
I&amp;rsquo;ll wait. ;)&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve done that, you should see something like the following (I&amp;rsquo;ve modified
the config, so the header will likely look different):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Learn how to package your unikernel with: kraft pkg --help
■▖   ▖■       _ _               __ _
■▚   ■▞  ___ (_) | __ __  __ _ ´ _) :_
▀■   ■▄ ´ _ `| | |/ /  _)´ _` | |_|  _)
▄▀▄ ▗▀▄| | | | |   (| | | (_) |  _) :_
 ▚▄■▄▞ ._, ._:_:_,\_._,  .__,_:_, \___)
                 Calypso 0.17.0~1a9f80b
Hello, World!
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;what-the-heck-just-happened&#34;&gt;What the heck just happened?&lt;/h2&gt;
&lt;p&gt;ok, so we have a bunch of things that just went on. kraftkit did a bunch of magic
for us, not all of which I understand quite yet, but here&amp;rsquo;s the high level:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read the &lt;code&gt;Kraftfile&lt;/code&gt; in the directory&lt;/li&gt;
&lt;li&gt;Wrote a kernel configuration into &lt;code&gt;.config.&amp;lt;appname&amp;gt;_&amp;lt;vmm&amp;gt;-&amp;lt;cpu arch&amp;gt;&lt;/code&gt;
(which in this case means &lt;code&gt;.config.helloworld_qemu-x86_64&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Pull the correct OCI images necessary to build the unikernel&lt;/li&gt;
&lt;li&gt;Build the microkernel, using &lt;code&gt;Makefile.uk&lt;/code&gt; effectively as an included &lt;code&gt;Makefile&lt;/code&gt; from
the main Makefile from the OCI image. &lt;code&gt;Makefile.uk&lt;/code&gt; includes our &lt;code&gt;helloworld.c&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Take the build unikernel image, and lauch QEMU, specifying that kernel image
should be used during the virtual machine boot&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From a developer point of view, the important things to know right now are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It begins with &lt;code&gt;Kraftfile&lt;/code&gt;, but we can largely ignore that unless we want to
upgrade the base code we&amp;rsquo;re building against&lt;/li&gt;
&lt;li&gt;Our &lt;code&gt;Makefile.uk&lt;/code&gt; file is pulled into the overall Makefile, so we can make
changes to the build here&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s a config file for the build&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unikraft is focused primarily on the &amp;ldquo;run stock off the shelf linux applications&amp;rdquo;
use case, we need to be aware that documentation here is scant, so be prepared
to dig, and also maybe read some source code. Originally, they did not have
the ability to run Linux binaries, so the build system as described here is all
they had. As a result, some material I&amp;rsquo;ve found, while useful, is also out of
date. &lt;a href=&#34;https://wiki.xenproject.org/images/2/23/Unikraft-buildsystem-compressed.pdf&#34;&gt;This presentation&lt;/a&gt;
helped &lt;strong&gt;a lot&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&#34;upgrading-the-base-code&#34;&gt;Upgrading the base code&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s get a simple thing out of the way first. How do I upgrade to a new version
of Unikraft? This was a problem I had in the last 3 months, as the version of
Unikraft I was working with did not seem to play nicely with any version of QEMU
that I tried. Luckily, there is a Unikraft version in the &lt;code&gt;Kraftfile&lt;/code&gt;. So, I thought,
the answer was &amp;ldquo;change that version number and rebuild&amp;rdquo;. Without detailed logging,
it looked like it worked. It did not.&lt;/p&gt;
&lt;p&gt;It turns out, that even after &lt;code&gt;kraft clean&lt;/code&gt;, there is a cache of build artifacts,
and the version specified in &lt;code&gt;Kraftfile&lt;/code&gt; is ignored. The solution is to &lt;code&gt;rm -rf .unikraft&lt;/code&gt;,
and this will force re-reading Kraftfile and pulling a new version. Another problem
is that arbitrary version shas as implied by the &lt;a href=&#34;https://unikraft.org/docs/cli/reference/kraftfile/v0.6#setting-a-specific-version&#34;&gt;documentation&lt;/a&gt;
is not actually possible. It appears as though the versions (or SHAs) you can use
the ones specified in the unikraft manifest file at this link:
&lt;a href=&#34;https://manifests.kraftkit.sh/unikraft.yaml&#34;&gt;https://manifests.kraftkit.sh/unikraft.yaml&lt;/a&gt;. This&amp;hellip;took me a while to work out.&lt;/p&gt;
&lt;h2 id=&#34;i-havent-done-anything-cool-yet&#34;&gt;I haven&amp;rsquo;t done anything cool yet!&lt;/h2&gt;
&lt;p&gt;Well, you sort of have. But that aside, I get it. We have learned a bit about
how all this works, and now, with that knowledge, we can get cracking. If you
want to skip &lt;strong&gt;way ahead&lt;/strong&gt;, you can look at my &lt;a href=&#34;https://git.lerch.org/lobo/unikraft-zig-native-hello&#34;&gt;example repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first thing I thought was &amp;ldquo;hey, I&amp;rsquo;ve now got a working application. Maybe the
easiest approach is to compile a static library, and just link it in&amp;rdquo;. I like
easy, so that&amp;rsquo;s what I tried.&lt;/p&gt;
&lt;p&gt;First attempt was to fire up &lt;code&gt;zig init&lt;/code&gt; on a subdirectory, add a line in
&lt;code&gt;build.zig&lt;/code&gt; to specify that we want to link with libc (&lt;code&gt;lib.linkLibC&lt;/code&gt;), build
it with &lt;code&gt;zig build&lt;/code&gt;, and copy the static library from &lt;code&gt;zig-out/lib&lt;/code&gt; into my
directory. ok, this is a good start, but now I need to link it. How is that
done? Well, I have a &lt;code&gt;Makefile.uk&lt;/code&gt;, and that presentation from 2019 I found,
and this gave me enough to stumble my way through. The trick is to add my
library in the &lt;code&gt;Makefile.uk&lt;/code&gt; like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Makefile&#34; data-lang=&#34;Makefile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UK_ALIBS-y &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;APPHELLOWORLD_BASE&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;/libziggy.a
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My file was called libziggy.a, for no real good reason. After doing this,
progress! And by progress, I mean a bunch of linker errors.&lt;/p&gt;
&lt;p&gt;Seeing all the undefined symbols, I first noticed there were zig-specific
symbols the linker was trying to resolve. Well, that won&amp;rsquo;t work, but I also
know how to deal with that. Another edit to &lt;code&gt;build.zig&lt;/code&gt; to specify that we
want the compiler runtime built in (&lt;code&gt;lib.bundle_compiler_rt = true&lt;/code&gt;),
and we were on our way&amp;hellip;to more failures. Specifically, we were now missing
the following symbols:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mmap64&lt;/li&gt;
&lt;li&gt;dl_iterate_phdr&lt;/li&gt;
&lt;li&gt;getcontext&lt;/li&gt;
&lt;li&gt;sigaction&lt;/li&gt;
&lt;li&gt;write&lt;/li&gt;
&lt;li&gt;close&lt;/li&gt;
&lt;li&gt;realpath&lt;/li&gt;
&lt;li&gt;read&lt;/li&gt;
&lt;li&gt;msync&lt;/li&gt;
&lt;li&gt;munmap&lt;/li&gt;
&lt;li&gt;environ&lt;/li&gt;
&lt;li&gt;openat64&lt;/li&gt;
&lt;li&gt;flock&lt;/li&gt;
&lt;li&gt;fstat64&lt;/li&gt;
&lt;li&gt;dl_iterate_phdr&lt;/li&gt;
&lt;li&gt;getenv&lt;/li&gt;
&lt;li&gt;isatty&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is painful, but manageable. Later I learned that these are not all &lt;strong&gt;used&lt;/strong&gt;
at runtime, but they are referenced in the library, and so must exist. To address
this, I spent a bit of time in the man pages, looking up function signatures and
adding empty implementations for each of the functions above. You can see that
work &lt;a href=&#34;https://git.lerch.org/lobo/unikraft-zig-native-hello/src/branch/master/undefined.c&#34;&gt;here&lt;/a&gt;
if needed, or want to copy it in as you&amp;rsquo;re following along. You will also note
that these aren&amp;rsquo;t really blank implementations. Each one will output
&amp;ldquo;unsupported function &lt;!-- raw HTML omitted --&gt; called&amp;rdquo;, then force a segfault to make
sure the VM crashes. That way we know what&amp;rsquo;s missing and can fix it.&lt;/p&gt;
&lt;p&gt;After doing this, I needed a way to get &lt;code&gt;undefined.c&lt;/code&gt; built and linked. We
already know how to do this, we can just copy and paste into &lt;code&gt;Makefile.uk&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Makefile&#34; data-lang=&#34;Makefile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;APPHELLOWORLD_SRCS-y &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;APPHELLOWORLD_BASE&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;/undefined.c
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With that&amp;hellip;success! The kernel is built and ready to run. As always, my code
is guaranteed to run the first time without any bugs. Especially &amp;ldquo;hello world&amp;rdquo;&amp;hellip;&lt;/p&gt;
&lt;h2 id=&#34;why-i-can-has-no-cheezburger&#34;&gt;Why I can has no cheezburger?&lt;/h2&gt;
&lt;p&gt;&amp;hellip;and, crash. But! The crash was not the terrible looping crash in QEMU that I
had seen in the old version. We got past that after running their sample.&lt;/p&gt;
&lt;p&gt;What was wrong, took me a while to figure out. I should note what the code
I wrote actually &lt;strong&gt;does&lt;/strong&gt;. &lt;code&gt;zig init&lt;/code&gt; provides a library with an &lt;code&gt;add&lt;/code&gt; function.
I modified that function with a logging statement (uncreatively another &amp;ldquo;hello
world&amp;rdquo;), and I called add from &lt;code&gt;helloworld.c&lt;/code&gt;, which looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#include&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;extern&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; result &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hello, World! 2+2=&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; ) buffer[i] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;0&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;puts&lt;/span&gt;(buffer);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The buffer stuff is dumb of course, but I didn&amp;rsquo;t want to mess with any additional
libc functions or memory allocations or&amp;hellip;anything. Just plain, dumb c outside
of what I was testing.&lt;/p&gt;
&lt;p&gt;Eventually, I noticed that if I compiled my zig library in any release mode
(e.g. &lt;code&gt;zig build -Doptimize=ReleaseSafe&lt;/code&gt;), it would actually work! And by work,
I mean crash on my &lt;code&gt;write()&lt;/code&gt; call in &lt;code&gt;undefined.c&lt;/code&gt;. Without my logging statement,
I could actually add 2 + 2 (I won&amp;rsquo;t spoil the answer here&amp;hellip;you&amp;rsquo;ll need to compile
you&amp;rsquo;re own unikernel and run it with QEMU to find out what that equals!),
and not crash. So more progress, but clearly more to do.&lt;/p&gt;
&lt;p&gt;I put my Debug mode crash problem on a shelf, and decided to compile in release
mode for the time being. I know my library was eventually calling write, so I
just needed to implement &lt;code&gt;write()&lt;/code&gt;. This eventually turned out to be the wrong
assumption, but I went down that path, pulled out my ancient c coding skills,
and got fancy about it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define ANSI_COLOR_RED     &amp;#34;\x1b[31m&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define ANSI_COLOR_GREEN   &amp;#34;\x1b[32m&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define ANSI_COLOR_YELLOW  &amp;#34;\x1b[33m&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define ANSI_COLOR_BLUE    &amp;#34;\x1b[34m&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define ANSI_COLOR_MAGENTA &amp;#34;\x1b[35m&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define ANSI_COLOR_CYAN    &amp;#34;\x1b[36m&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define ANSI_COLOR_RESET   &amp;#34;\x1b[0m&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;write&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; fd, &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;buf, &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; count){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;UNUSED&lt;/span&gt;(fd);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;UNUSED&lt;/span&gt;(buf);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;UNUSED&lt;/span&gt;(count);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;color;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; (fd) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// stdout
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;            color &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ANSI_COLOR_RESET;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            color &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ANSI_COLOR_RED;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;write called on unsupported file descriptor: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; fd_buf[&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fd = &amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            fd_buf[&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;0&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; fd;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            fd_buf[&lt;span style=&#34;color:#ae81ff&#34;&gt;6&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;puts&lt;/span&gt;(fd_buf);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;SEGFAULT&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (fd &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(color);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (fd &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(stderr): &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%.*s&amp;#34;&lt;/span&gt;, (&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;)count, buf);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (fd &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;printf&lt;/span&gt;(ANSI_COLOR_RESET);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; count;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once I worked through this, as long as I built with &lt;code&gt;zig build -Doptimize=ReleaseSafe&lt;/code&gt;,
I was chillin&#39;!&lt;/p&gt;
&lt;h2 id=&#34;i-really-cant-write-to-the-console-how-is-helloworldc-doing-this&#34;&gt;I really can&amp;rsquo;t write to the console? How is helloworld.c doing this?&lt;/h2&gt;
&lt;p&gt;ok, so I know helloworld.c is writing to the console. They&amp;rsquo;re using &lt;code&gt;puts&lt;/code&gt;, but
console output is a clear use case, so it must be that Unikraft has solved this
problem in the aggregate. What gives?&lt;/p&gt;
&lt;p&gt;A bunch of research later, and some blog post, it might have been &lt;a href=&#34;https://unikraft.org/guides/internals&#34;&gt;this guide&lt;/a&gt;, but I&amp;rsquo;m not 100% sure, and I started
really poking through &lt;code&gt;kraft menuconfig&lt;/code&gt;. Stumbling into their &amp;ldquo;Library Configuration&amp;rdquo;
section, I struck pay dirt. &lt;code&gt;posix-tty&lt;/code&gt; said &amp;ldquo;Support for stdin/out/err&amp;rdquo;, and
when I turned that on, I got a build error. This&amp;hellip;was a good thing, because
specifically it was complaining about duplicate a duplicate symbol name for
&lt;code&gt;write()&lt;/code&gt;. I &lt;code&gt;#ifdef&lt;/code&gt;&amp;rsquo;d my way around the build error, and got output! So,
turning on the correct options in the Library Configuration section &lt;strong&gt;should&lt;/strong&gt;
eliminate my need for &lt;code&gt;undefined.c&lt;/code&gt; entirely. This is still on my TODO list, or
at least setting up the correct &lt;code&gt;#ifdef&lt;/code&gt; statements and error messages is on
my TODO list. I&amp;rsquo;d love to be able to take a default Unikraft configuration and
just run zig code. Also, I like that my &lt;code&gt;stdout&lt;/code&gt; shows up in red.&lt;/p&gt;
&lt;h2 id=&#34;loose-ends&#34;&gt;Loose ends&lt;/h2&gt;
&lt;p&gt;Two things were left to do. 1) track down Debug mode builds, and 2) compile
and run &amp;ldquo;the zig way&amp;rdquo;, which is &lt;code&gt;zig build&lt;/code&gt; and &lt;code&gt;zig build run&lt;/code&gt;. For debug mode,
the problem eventually traced to the libc call &lt;code&gt;gettid()&lt;/code&gt;, which is implemented
if you turn on the new posix-process stuff in &lt;code&gt;kraft menuconfig&lt;/code&gt;. But zig does
not call that, preferring the direct Linux syscall, and I haven&amp;rsquo;t figured out
the Linux syscall shim in Unikraft yet. Zig does not call it, as zig supports
glibc going back to a time when &lt;code&gt;gettid()&lt;/code&gt; did not exist in libc. It only looks
for the current thread ID when performing locks in Debug mode, which happens to
happen when performing a log function call. So&amp;hellip;that&amp;rsquo;s the root cause, but
I don&amp;rsquo;t really have a reasonable path forward, other than &lt;a href=&#34;https://github.com/ziglang/zig/issues/20546&#34;&gt;asking other zig
contributors what they think&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Compiling and running was much more straightforward. I will spare the details
here, but my &lt;a href=&#34;https://git.lerch.org/lobo/unikraft-zig-native-hello/src/branch/master/build.zig&#34;&gt;build.zig&lt;/a&gt;
in the example repo has the details. Note that I&amp;rsquo;m not doing anything special
to avoid dependencies on the system, so you still need QEMU and kraftkit
installed. But I do have a (mostly) working &lt;code&gt;zig build distclean&lt;/code&gt; build step
that will also remove the cached &lt;code&gt;.unikraft&lt;/code&gt; directory that really confused
my debugging.&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;I&amp;rsquo;m kidding/not kidding on the overhead. In the grand scheme of things,
unikernels eliminate so much traditional overhead, the small amount of extra
time to parse and load elf binaries and provide the other compatibility is fine
for most use cases.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>unikernels</title>
      <link>https://emil.lerch.org/unikernels/</link>
      <pubDate>Tue, 09 Apr 2024 17:15:30 -0700</pubDate>
      
      <guid>https://emil.lerch.org/unikernels/</guid>
      <description>&lt;h1 id=&#34;unikernels&#34;&gt;Unikernels&lt;/h1&gt;
&lt;p&gt;I recently saw a &lt;a href=&#34;https://news.ycombinator.com/item?id=39902949&#34;&gt;notice on Hacker News&lt;/a&gt;
talking about &lt;a href=&#34;https://kraft.cloud/&#34;&gt;KraftCloud&lt;/a&gt;. I&amp;rsquo;ve seen previous news about
&lt;a href=&#34;https://www.unikraft.io&#34;&gt;UniKraft&lt;/a&gt; and Unikernels in general and have more
than a passing interest in the technology. This news item had me take another
look at the state of the technology, and what I learned was interesting.&lt;/p&gt;
&lt;h2 id=&#34;what-is-a-unikernel&#34;&gt;What is a Unikernel?&lt;/h2&gt;
&lt;p&gt;Typically, an application runs on an operating system, which runs on hardware.
Sometimes, the operating system is running in a virtual machine, which is
supplied by another operating system (called a hypervisor), running on hardware.
Sometimes, the application is running in a container on and operating system.
Sometimes, containers and/or virtual machines can be nested. It all makes
sense based on what you need to do, but this can get confusing, and is often
unnecessary. For example, if I want a server that runs a microservice and
calls other microservices, do I really need a full operating system that has
everything needed to run the latest AAA game while watching streaming video
and chatting with my friends? By eliminating everything besides what we need
to run my microservice, we gain a lot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Less storage for the operating system, application, and dependencies&lt;/li&gt;
&lt;li&gt;Less memory usage as we lose all the stuff loaded &amp;ldquo;just in case&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Less CPU as we don&amp;rsquo;t have services we don&amp;rsquo;t need&lt;/li&gt;
&lt;li&gt;Less attack surface for attackers to leverage&lt;/li&gt;
&lt;li&gt;Faster initialization (boot)&lt;/li&gt;
&lt;li&gt;Immutable deployment&lt;/li&gt;
&lt;li&gt;Performance through the ability to tailor the OS to the needs of the application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can achieve these benefits in a few ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Smaller docker images&lt;/li&gt;
&lt;li&gt;Stripped down operating systems&lt;/li&gt;
&lt;li&gt;Minimalistic hypervisors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But, there&amp;rsquo;s another way. What if we build our application directly into our
operating system? This takes minimalism to the extreme, or logical confusion,
depending on your view. This idea has been around since the mid-90&amp;rsquo;s, but
the term &amp;ldquo;Unikernel&amp;rdquo; appears to have been coined &lt;a href=&#34;https://mort.io/publications/pdf/asplos13-unikernels.pdf&#34;&gt;in a 2013 paper&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;current-landscape&#34;&gt;Current Landscape&lt;/h2&gt;
&lt;p&gt;Since 2013, several research projects have been created around Unikernels.
A good list &lt;a href=&#34;https://github.com/cetic/unikernels#comparing-solutions&#34;&gt;can be found on GitHub&lt;/a&gt;.
You&amp;rsquo;ll notice that on each kernel has a specific set of languages supported.
This is a major drawback to most Unikernels (yes&amp;hellip;that&amp;rsquo;s foreshadowing).
Since the application is literally compiled with the kernel, the kernel and
the application must be built together, at the same time, with a common set
of tools. Typically, the languages are fairly low level, primarily because
Unikernels must be written in languages that can speak to &amp;ldquo;hardware&amp;rdquo; (where
hardware might be a virtual machine provided by KVM, Xen, VirtualBox, Hyper-V).&lt;/p&gt;
&lt;p&gt;In my opinion, this has been the major drawback that has kept Unikernels in
a niche space. Something for me personally to keep an eye on, but not a technology
to get excited about.&lt;/p&gt;
&lt;p&gt;When I started to revisit the topic of Unikernels when this news from Unikraft
was announced, I took a peek, and noticed they are supporting &amp;ldquo;any&amp;rdquo; docker
application with their Unikernel. How does this work, if the application
is immutably bundled with the kernel?&lt;/p&gt;
&lt;p&gt;I took a look, and there are three major Unikernel projects all trying to solve
the developer experience and operational experience for Unikernels. &lt;a href=&#34;https://github.com/cloudius-systems/osv&#34;&gt;OSv&lt;/a&gt;
was first announced in 2014, took a break from 2015-2018, and has been active
since. &lt;a href=&#34;https://github.com/nanovms/nanos&#34;&gt;Nanos&lt;/a&gt; and
&lt;a href=&#34;https://github.com/unikraft/unikraft&#34;&gt;Unikraft&lt;/a&gt; trace their roots back to 2017.
All three have this magical ability to run multiple languages&amp;hellip;so what gives?&lt;/p&gt;
&lt;h2 id=&#34;enter-docker&#34;&gt;Enter Docker&lt;/h2&gt;
&lt;p&gt;Docker has become enormously popular since its launch in 2013. Developers and
operations appreciated the benefits of reproducibility and immutability of
docker images. But Docker was interested in creating an open and level playing
field, and eventually standardized, in 2017, the format used to create docker
images. So, the industry had a standard format for reproducible images that
could be used for Unikernels, and Docker also reinforced the trend toward
Linux-based microservices.&lt;/p&gt;
&lt;p&gt;Without talking to the people on these projects, my assumption is that, in
2017, a few folks had the idea to build a Unikernel with a standard application.
That application&amp;rsquo;s responsibility would be to load and run Linux binaries.
Linux binaries use a standard &lt;a href=&#34;https://en.wikipedia.org/wiki/Executable_and_Linkable_Format&#34;&gt;ELF binary format&lt;/a&gt;,
access the kernel through a &lt;a href=&#34;https://en.wikipedia.org/wiki/Linux_kernel_interfaces#System_call_interface_of_the_Linux_kernel&#34;&gt;standardized set of syscalls&lt;/a&gt;,
and with OCI, have a standard way of packaging the binaries.&lt;/p&gt;
&lt;p&gt;All three have gone down this path, and in 2020, unikernels were split, from
a taxonomy perspective, into &lt;a href=&#34;https://dl.acm.org/doi/fullHtml/10.1145/3624486.3624492#sec-5&#34;&gt;&amp;ldquo;Language-based unikernels&amp;rdquo; and &amp;ldquo;POSIX-like unikernels&amp;rdquo;&lt;/a&gt;. Evenutally,
researchers have said, &amp;ldquo;rather than create a Unikernel from the ground up
with Linux support, why don&amp;rsquo;t we strip down Linux instead?&amp;rdquo;. Thus was born,
in May 2023, the &lt;a href=&#34;https://dspace.mit.edu/bitstream/handle/1721.1/150839/3552326.3587458.pdf?sequence=1&#34;&gt;UniKernel Linux project&lt;/a&gt;. I could not find boot time measurements
for this project, but the &lt;a href=&#34;https://firecracker-microvm.github.io/&#34;&gt;Firecracker&lt;/a&gt;
project claims to have a custom linux kernel booting in 125ms. I&amp;rsquo;m a bit skeptical
of the UKL&amp;rsquo;s project ability to compete on that particular metric as Unikraft,
for example, claims boots in 6ms. My personal observations on Unikraft are that
6ms is, in fact, about correct.&lt;/p&gt;
&lt;p&gt;Why, though, is it important to boot your machine this quickly? Well, the primary
thing for me is that 119ms, or the difference between boot times between
optimized Linux and, in this case, Unikraft, is also the difference between
providing a microservice that needs to run 100% of the time and one that can
simply boot up, serve a request, and then shut down completely&amp;hellip;&lt;/p&gt;
&lt;h2 id=&#34;unikraft&#34;&gt;Unikraft&lt;/h2&gt;
&lt;p&gt;All three of what I consider to be the major players in the Unikernel space
offer the same features. All provide Linux binary support with an orchestrator
that provides someone with a CLI that feels like docker. Compiling a native
application (one that does not use the ELF loader and Linux syscall emulation)
is left for the truly daring in each. OSv and Nanos do not provide directions
for this at all, and while Unikraft does, I was unable to get a functional
kernel on my machine, which was probably the &lt;a href=&#34;https://github.com/unikraft/unikraft/issues/1371&#34;&gt;same issue as reported here&lt;/a&gt;. Of the three, I&amp;rsquo;m most
excited about Unikraft, for a few reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;They have a commercial business in the making, based on the core Unikernel
technology. They need the open source kernel fast, compatible, and developer
friendly to succeed. They have VC funding to pay people to make this happen,
in addition to the open source community. The primary backer of Nanos sells
command and control. The primary backer of OSv sells&amp;hellip;nothing at the moment?&lt;/li&gt;
&lt;li&gt;It seems like they have had a lot more momentum then the others.&lt;/li&gt;
&lt;li&gt;They are still open to native apps, and their Linux binary support is
&lt;a href=&#34;https://github.com/unikraft/app-elfloader&#34;&gt;actually a separate repo from the core&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On their commercial offering, I joined the beta, and I&amp;rsquo;m impressed. The performance
was as advertised, the developer experience was great, and the &amp;ldquo;scale to zero&amp;rdquo;
works well. I haven&amp;rsquo;t tried it myself yet, but &amp;ldquo;scale to zero&amp;rdquo; should also
work with non-HTTP connections. Their documentation describes what is effectively
a load balancer in front of your unikernel deployments, so a service is always
there to listen for new connections and trigger your unikernel.&lt;/p&gt;
&lt;p&gt;Is it ready to use? I would say no, not yet, at least not in a general sense.
The idea here is to have Linux binaries &amp;ldquo;just work&amp;rdquo;, and without support for
the &lt;code&gt;fork()&lt;/code&gt; system call, we get pretty limited. If you craft your binary with
the assumption of working in Unikraft, then yes. But that&amp;rsquo;s not their goal,
and it really shouldn&amp;rsquo;t be ours. While I can&amp;rsquo;t find it on Unikraft&amp;rsquo;s roadmap,
one of their core team has stated that &lt;a href=&#34;https://news.ycombinator.com/item?id=39904144&#34;&gt;fork is coming soon&lt;/a&gt;. I know I will be watching this
space closely.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Neomutt setup</title>
      <link>https://emil.lerch.org/neomutt-setup/</link>
      <pubDate>Sat, 13 Jan 2024 11:43:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/neomutt-setup/</guid>
      <description>&lt;h1 id=&#34;neomutt&#34;&gt;Neomutt&lt;/h1&gt;
&lt;p&gt;In my &lt;a href=&#34;https://emil.lerch.org/smtp-and-email-notes/&#34;&gt;last post&lt;/a&gt; I mentioned I am
using neomutt for email. This seems completely archaic&amp;hellip;how can one do this in
modern times? Wasn&amp;rsquo;t this built for pre-world wide web times, before HTML was
even invented?&lt;/p&gt;
&lt;p&gt;Well, yes, but it can still handle all the modern crazy, and for those not
afraid of a terminal and keyboard, it has several advantages:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&amp;ldquo;Modern&amp;rdquo; mail clients don&amp;rsquo;t work in the terminal&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Modern&amp;rdquo; mail clients need to be driven by using the mouse. In fairness, both
Outlook and GMail have copius keyboard shortcuts, but they are clearly mouse-first&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Modern&amp;rdquo; mail clients take a lot more memory. All my mail, starting from the &amp;rsquo;90s,
is available to me in neomutt. Memory usage is 339M (RSS). Opening gmail,
once my fan spins down and my browser is responsive again, creates a tab that
consumes 800M. Outlook on my work machine has 59 threads consuming 589M working
set (833M private bytes virtual memory)&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Modern&amp;rdquo; mail clients &lt;a href=&#34;https://proton.me/blog/outlook-is-microsofts-new-data-collection-service&#34;&gt;include tracking&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Modern&amp;rdquo; mail clients display html emails that include tracking mechanisms
so marketers can determine open rates for email.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ok, so if these advantages are interesting, the question naturally becomes,
&amp;ldquo;how on earth do you deal with email, 99% of which these days is html?&amp;rdquo;. This is
a good question, and to be clear here, I&amp;rsquo;m only going to address &lt;strong&gt;inbound&lt;/strong&gt;
email. When I send email, it&amp;rsquo;s text only&amp;hellip;I have need to format email probably
less than once/year, even at work. If I ever have that need for personal email,
I&amp;rsquo;ll likely just use nextcloud or something. I haven&amp;rsquo;t even run into the issue.&lt;/p&gt;
&lt;p&gt;At a high level, this the combination of technology I use:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mail client: neomutt&lt;/li&gt;
&lt;li&gt;Terminal image support: This is provided through
&lt;a href=&#34;https://en.wikipedia.org/wiki/Sixel&#34;&gt;sixel&lt;/a&gt; support. I used to use
&lt;a href=&#34;https://st.suckless.org/&#34;&gt;st&lt;/a&gt;, now I use &lt;a href=&#34;https://github.com/arakiken/mlterm&#34;&gt;mlterm&lt;/a&gt;.
They both have advantages/disadvantages. Honestly, I have yet to really find
a terminal I&amp;rsquo;m 100% happy with. More full featured (but heavier weight)
sixel-enabled terminals can be found at &lt;a href=&#34;https://www.arewesixelyet.com/&#34;&gt;https://www.arewesixelyet.com/&lt;/a&gt;.
Common terminals for normal people are xterm, iTerm2, and konsole. Also,
tmux and Visual Studio Code both support sixels.&lt;/li&gt;
&lt;li&gt;Terminal based browser: &lt;a href=&#34;https://github.com/tats/w3m&#34;&gt;w3m&lt;/a&gt;. I&amp;rsquo;m not particular
on this, it&amp;rsquo;s possible that lynx or something else might work&amp;hellip;I just don&amp;rsquo;t
do a lot of terminal-based web browsing here, I just want to view an email,
but graduate to a normal browser for normal browsing.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For a while I had a basic setup going, but I re-visited recently and I now have
a configuration I&amp;rsquo;m pretty happy with. A lot of the credit goes to &lt;a href=&#34;https://www.reddit.com/r/commandline/comments/z7vkwn/how_can_i_use_w3m_with_html_that_has_images_in/&#34;&gt;this
reddit post&lt;/a&gt;,
and the information below is largely a re-post of the comment I made there.&lt;/p&gt;
&lt;p&gt;With the software above installed, the configuration needs to be set up. &lt;code&gt;~/.mailcap&lt;/code&gt;
is a configuration file that describes what should happen when a mail client
sees various types of mail. This drives a lot of the behavior. I&amp;rsquo;ve created a
mailcap file that looks like the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# The first one here is triggered when going in to view the attachments, then pressing &amp;#39;m&amp;#39; on the text/html
# -sixel changes w3m behavior a bit. Without it, images will show up, but only when you do something to the screen
# having the option shows the images immediately
text/html;                         w3m -sixel -o auto_image=TRUE -o display_image=1 -T text/html %s; nametemplate=%s.html; needsterminal

# This second one is chosen by auto_view due to the copiousoutput tag
text/html;                         w3m -I %{charset} -T text/html -cols 140 -o display_link_number=1 -dump; copiousoutput

# The third, non-existent one is going in to view the attachments,
# then press &amp;#39;V&amp;#39; on the text/html attachment, which opens in a proper browser
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The comments above should be pretty clear, but this gives me the following behavior in neomutt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt; from the index, shows HTML through w3m, with links at the bottom.
No images or color. This is fine for 90% of my needs, and is privacy respecting.
This behavior is driven from neomutt auto view, which looks for the
&lt;code&gt;copiousoutput&lt;/code&gt; attribute, which needs to be the &lt;strong&gt;second&lt;/strong&gt; entry in mailcap
(otherwise it would be the default for everything, which I don&amp;rsquo;t want)&lt;/li&gt;
&lt;li&gt;Going into the attachment list (&lt;code&gt;v&lt;/code&gt; from the index) and using &lt;code&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt;
or &lt;code&gt;m&lt;/code&gt; on the text/html opens in w3m, in full glory, with all images. Oddly,
I thought I needed &lt;code&gt;m&lt;/code&gt; in this situation, and was surprised to find out
&lt;code&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt; works. This will send tracking pixel data and whatever other
crazy the sender is doing, but I have to take affirmative action to view
email in this way.&lt;/li&gt;
&lt;li&gt;Going into the attachment list (&lt;code&gt;v&lt;/code&gt; from the index) and using &lt;code&gt;V&lt;/code&gt; on the
text/html will open in my actual browser, which seems necessary for a lot
of unsubscribe links in particular for some reason&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last bullet (using &lt;code&gt;V&lt;/code&gt; on the attachment list) is enabled through the
following macro defined in my neomutt config (I use ~/.config/mutt/muttrc) as
my configuration theoretically will work in either mutt or neomutt:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;macro attach &#39;V&#39; &amp;quot;&amp;lt;pipe-entry&amp;gt;iconv -c --to-code=UTF8 &amp;gt; ~/.cache/mutt-mail.html&amp;lt;enter&amp;gt;&amp;lt;shell-escape&amp;gt;open ~/.cache/mutt-mail.html&amp;lt;enter&amp;gt;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also have &lt;code&gt;confirm_qq false&lt;/code&gt; defined in &lt;code&gt;.w3m/config&lt;/code&gt;. This simply prevents
w3m from confirming I want to quit.&lt;/p&gt;
&lt;p&gt;This seems pretty good so far and gives me a nice way to progressively handle my html mail:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;90% of the time, I just hit &lt;code&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt;. I can see the link text, and I don&amp;rsquo;t send pixel tracking data&lt;/li&gt;
&lt;li&gt;8%, go into attachment list and hit &lt;code&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt; to see images. At this point,
I&amp;rsquo;m sending tracking data, which I&amp;rsquo;m consciously doing in a trade off for
information I find more valuable&lt;/li&gt;
&lt;li&gt;2%, go into attachment list and hit &lt;code&gt;V&lt;/code&gt; because I really need to interact
with this thing beyond w3m. Usually unsubscribe links, but it could also
be newsletters with links to source material (w3m is usually good enough
for this though)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s it! In the end, it&amp;rsquo;s not a lot of magic config, but it&amp;rsquo;s clearly tricky
to work out. Also, this works well for how I work&amp;hellip;others are looking for
different user experience, and will probably not like this setup&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>SMTP and email notes</title>
      <link>https://emil.lerch.org/smtp-and-email-notes/</link>
      <pubDate>Tue, 07 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://emil.lerch.org/smtp-and-email-notes/</guid>
      <description>&lt;h1 id=&#34;smtp-and-email-notes&#34;&gt;SMTP and email notes&lt;/h1&gt;
&lt;p&gt;This is a general post to document what I&amp;rsquo;ve been learning while setting up my
own email server. It&amp;rsquo;s been a long time since working with SMTP directly,
especially with things like SPF, DMARC, and DKIM. A few general feelings
of this process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s not terrible! Running your own server is something that general Internet
advice says DO NOT DO. But that advice, at least so far, seems wrong. I can
send to gmail without issue, and get 10/10 on &lt;a href=&#34;https://mail-tester.com&#34;&gt;mail-tester.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s a lot of bad advice on the Internet&lt;/li&gt;
&lt;li&gt;The system is not magic&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s pretty flexible&lt;/li&gt;
&lt;li&gt;Security has improved a lot since the days of &amp;ldquo;telnet to port 25&amp;rdquo;!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s start with security, because with all the spam out there, email kind of
begins and ends there&lt;/p&gt;
&lt;h2 id=&#34;security&#34;&gt;Security&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with &amp;ldquo;telnet to port 25&amp;rdquo;. Port 25 is in use, but connections can
be TLS encrypted over that port. From the server side, we can require it.
Also, authentication of course. TLS on all the other ports, and encryption
in transit is covered.&lt;/p&gt;
&lt;p&gt;Connecting to other servers, services also check other things. In order of
importance, I find quite a bit of poor advice on the Internet.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reverse DNS, or PTR records&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unlike what I read before hand, &lt;strong&gt;these are critical&lt;/strong&gt;. Basically, you can
have a  near perfect score, and nobody will deliver your email without this.
Of course, it&amp;rsquo;s the owner of the IP range that needs to do this, so setting
that up will vary based on the hosting provider you use.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DMARC, SPF, DKIM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only DKIM is marginally a problem. SPF, and especially DKIM, provide indications
of the integrity of the mail. DMARC is simply there to define your policy of
what should be done should SPF or DKIM fail. That&amp;rsquo;s the high level, but a good
first read on these technologies is &lt;a href=&#34;https://www.cloudflare.com/learning/email-security/dmarc-dkim-spf/&#34;&gt;here on Cloudflare&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;DKIM is the most finicky, mostly because you have to have everything aligned for
it to work. But, it&amp;rsquo;s basic cryptography. You store the public key in DNS,
along with a &amp;ldquo;selector&amp;rdquo;, and your server software signs the email with the
private key. The email header describes what other headers are part of the
signature, so your email can be sent through 10 relays and it doesn&amp;rsquo;t matter
at all unless one of them messes with those headers or the email itself.&lt;/p&gt;
&lt;p&gt;The most fun thing IMHO is that selector. I was initially confused why that&amp;rsquo;s
needed, but the answer is relatively simple. If you use multiple servers or
services, you don&amp;rsquo;t need to pass multiple keys around. You can also have multiple
private keys to do rotation. The server provides the selector in the DKIM
header, so the domain lookup will find the right DNS record for that particular
email. More on this later, but it makes things pretty flexible.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hosting provider&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gets a bit tough. The big cloud providers all have various restrictions
on using their servers to send email. I&amp;rsquo;m sure they want good IP reputation
(see below), but I suspect other reasons exist and I don&amp;rsquo;t want to speculate
too much. I believe DigitalOcean and Vultr will allow it, but ultimately I found
&lt;a href=&#34;https://www.georgedatacenter.com/&#34;&gt;George DC&lt;/a&gt; had a pretty good deal, and is
fine with sending email, as long as it&amp;rsquo;s not for spam. I even submitted a ticket
to update the reverse DNS and it was done in about an hour.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IP reputation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I haven&amp;rsquo;t had to worry about this too much, but I did notice two or three hits
on IP reputation lists. They were old and it probably doesn&amp;rsquo;t matter, but I
wanted to know how to clean that up. It&amp;rsquo;s both easy and painful. Easy, because
you typically just need to go to the place that claims you have a nefarious
IP address and click a button to delist. Painful because you have to do it
at every place that reports your IP address, and many of those sites need you
to establish an account. One required me to submit a ticket (which seems to have
been handled without human intervention). Note that I did not try to use my
home Internet connection for any experimentation - the Internet says that&amp;rsquo;s a
really bad idea, and I tend to agree it&amp;rsquo;s probably not worth trying.&lt;/p&gt;
&lt;h2 id=&#34;software&#34;&gt;Software&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m using &lt;a href=&#34;https://github.com/docker-mailserver/docker-mailserver&#34;&gt;docker-mailserver&lt;/a&gt;,
which sets up most things for you. At the moment they seem to be transitioning
some of the component software, but the docs were reasonable and it&amp;rsquo;s possible
to make basically whatever configuration changes you need. They don&amp;rsquo;t hide
their component software, so if you want to make changes, you&amp;rsquo;ll be digging
into postfix, dovecot, etc. documentation. But, postfix in particular is
a good battle tested server that is extremely flexible. Your mileage may vary,
especially since this does not include any kind of web-based email interface. I
don&amp;rsquo;t really need that, but if you do, there are other alternatives to consider
like mailcow. I cannot vouch for anything else though - if I want web-based,
I can just connect my &lt;a href=&#34;https://nextcloud.com/&#34;&gt;nextcloud&lt;/a&gt; server&amp;rsquo;s mail app
to it through IMAP.&lt;/p&gt;
&lt;h2 id=&#34;advanced-terminology-and-use-cases&#34;&gt;Advanced terminology and use cases&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m interested in something called &amp;ldquo;split routing&amp;rdquo;, where one mail server
handles some email, and forwards the rest to another mail server. SMTP is
actually very decentralized, though, so the idea of &amp;ldquo;this is your mail server&amp;rdquo;
isn&amp;rsquo;t actually really a thing. Yes, there is a mail server that hosts your
mail box, but really, anyone can do anything. The flexibility can be confusing,
but it&amp;rsquo;s also pretty powerful.&lt;/p&gt;
&lt;p&gt;One thing that I have really comes to terms with is the idea of an MTA, or
mail transfer agent. Typically, we talk about mail servers, but technically,
they are mail transfer agents. A mail comes in, and if the software knows that
the mail is stored locally, that&amp;rsquo;s where it transfers it to. Otherwise, it
determines the next place to go to. Whether that mail comes in from an IMAP
connection, SMTP, or another SMTP server, is immaterial really. It just matters
&amp;ldquo;where does this mail need to go&amp;rdquo;. In postfix, this means that all we need to
do for &amp;ldquo;split routing&amp;rdquo; is define overrides in transport maps for emails we
want to override. Literally&amp;hellip;that&amp;rsquo;s it. The stuff we don&amp;rsquo;t override goes
to where it&amp;rsquo;s supposed to go to. With a lot of help from &lt;a href=&#34;https://www.linuxbabe.com/mail-server/postfix-transport-map-relay-map-flexible-email-delivery&#34;&gt;LinuxBabe&lt;/a&gt;,
I set up split routing, and it basically looks like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My DNS MX record is my server&lt;/li&gt;
&lt;li&gt;I signed up with &lt;a href=&#34;kingmailer.org&#34;&gt;kingmailer.org&lt;/a&gt; for a trial. They tell me
to go set up an MX record. I &lt;em&gt;IGNORE&lt;/em&gt; that.&lt;/li&gt;
&lt;li&gt;I set up a &lt;code&gt;kingmailer@&lt;/code&gt; email address on my domain, in kingmailer.&lt;/li&gt;
&lt;li&gt;I set up a &lt;code&gt;user@&lt;/code&gt; email address on my domain, but only on my own server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note, that at this point, kingmailer doesn&amp;rsquo;t know there&amp;rsquo;s another mail server,
and does not know about &lt;code&gt;user@&lt;/code&gt; email address. Right now, the server that
DNS points at, which will handle &lt;strong&gt;all&lt;/strong&gt; email, has no idea of a &lt;code&gt;kingmailer@&lt;/code&gt;
email address. Time to make it aware.&lt;/p&gt;
&lt;p&gt;docker-mailserver has a mechanism for overrides, so this bit is specific to that
software, but I simply dump a &lt;code&gt;postfix-main.cf&lt;/code&gt; into the configuration directory,
with a single line:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;transport_maps = texthash:/tmp/docker-mailserver/postfix-transport_map.cf
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;docker-mailserver default docker compose file will mount a config volume in that
location. The transport map itself is also relatively simple:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kingmail@example.com     smtp:[kingmailer.org]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Inbound emails will still be rejected though because there is no local account,
so using &lt;code&gt;docker exec -it mailserver setup add email kingmail@example.com&lt;/code&gt; makes
quick work of that. All that script does though is add a line to the account
configuration and ask postfix to reload.&lt;/p&gt;
&lt;p&gt;&amp;hellip;and&amp;hellip;that&amp;rsquo;s it! Because I specify the server here (with square brackets to
prevent more processing), my server will not bother looking at MX records, it
will just send the mail to the right place.  LinuxBabe recommends adding your
domains into this map with a value of local, and I played around with that, but
kept getting errors. Leaving it out seems to work just fine, and my assumption
is that the default, at least in docker-mailserver, is not a &lt;code&gt;local&lt;/code&gt; transport.&lt;/p&gt;
&lt;p&gt;One thing I really wanted to do, but again, it goes against the general wisdom
of the Internet, is get a unified inbox in my actual email client. I&amp;rsquo;ve gone
back to mutt (actually, &lt;a href=&#34;https://neomutt.org&#34;&gt;neomutt&lt;/a&gt;) which has the disadvantage
of that HTML email is inconvenient, but also has a strong advantage in that
HTML email is inconvenient. So many trackers, etc that are no longer automatically
triggered. I&amp;rsquo;ve been working with neomutt over IMAP with gmail for over a year
now, and rarely need the actual web version. &amp;ldquo;Clicking links&amp;rdquo; is something
that&amp;rsquo;s not as pleasant as I would like, but all in all, I&amp;rsquo;m not sure HTML email
was all that great an idea in the first place. ;-)&lt;/p&gt;
&lt;p&gt;Mutt/Neomutt don&amp;rsquo;t really handle a unified inbox very well, so I figured out
a way to do this by syncronizing my postfix data directly into my mail directory,
thus violating not one, but two pieces of generally accepted wisdom. First,
&amp;ldquo;you really don&amp;rsquo;t want to have two accounts in the same mail directory&amp;rdquo;, and
second, &amp;ldquo;don&amp;rsquo;t use &lt;a href=&#34;https://www.syncthing.net&#34;&gt;syncthing&lt;/a&gt; to synchronize a
subdirectory of a synchronized directory&amp;rdquo;. The latter came as a warning message
directly in the UI of the software, so that was a little scary, but I&amp;rsquo;m ok&amp;hellip;
more on this in a second.&lt;/p&gt;
&lt;p&gt;The first advice was legitimate. My mail directories use &lt;a href=&#34;https://isync.sourceforge.io/&#34;&gt;isync (aka
mbsync)&lt;/a&gt; to synchronize all my imap folders in
Gmail to my local storage. If I&amp;rsquo;m going to keep the mail separated, I don&amp;rsquo;t
want my postfix mail to get uploaded, so I needed mbsync to ignore the postfix
mail. I also needed syncthing to ignore gmail-sourced mail. I have decades of
email in gmail, and I do not need that on my VPS with only 40GB disk. For the
first problem, I actually forked the isync source code at the latest release,
then added a &lt;a href=&#34;https://git.lerch.org/lobo/isync/commit/6faf91a8068a14ce0fd5ac9695b567a569cad2c1&#34;&gt;3 line patch&lt;/a&gt;
to look for a substring of the file name, and ignore it. As all postfix-sourced
email ends up with the server host name in the file name (but Gmail does not),
I could use that to let mbsync ignore my postfix data. On the syncthing side,
I used the following ignore pattern:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;??????????.*.????????????,U=*:?,*
dovecot*
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That first pattern looks nuts, but I basically tried to scope to exactly what
matched the mbsync stuff. The dovecot line is due to some dovecot accounting
files (I&amp;rsquo;ve been saying postfix, but the files are actually managed by dovecot,
which also handles imap).&lt;/p&gt;
&lt;p&gt;The last issue is the syncthing warning about syncing a subfolder of a synced
folder. This is ok in this circumstance. I&amp;rsquo;m sharing my overall mail folder
with every device I want to use neomutt on (this allows me offline access when
I&amp;rsquo;m traveling). However, just my inbox folder/subfolders is being synced to
my mail server, and shouldn&amp;rsquo;t be shared anywhere else. Mutt/neomutt can happily
think it&amp;rsquo;s working with a local mail directory, while everything I do is
transparently synced back to gmail (for gmail-based email), or to my mail server
(for email stored there).&lt;/p&gt;
&lt;h2 id=&#34;tools&#34;&gt;Tools&lt;/h2&gt;
&lt;p&gt;I mentioned &lt;a href=&#34;https://kingmailer.co/&#34;&gt;kingmailer&lt;/a&gt; a couple times. I&amp;rsquo;m sure there
are a bunch of reasonable services, but kingmailer had a 14 day trial that did
everything needed to get comfortable with the process.&lt;/p&gt;
&lt;p&gt;Debugging DKIM (is my DNS updated properly? Am I loading the right private key?
What about that bug in docker-mailserver where the config doesn&amp;rsquo;t persist
properly? Is docker-mailserver using OpenDKM or rspamd?) was a bit painful. I
found &lt;a href=&#34;https://appmaildev.com/en/dkim&#34;&gt;https://appmaildev.com/en/dkim&lt;/a&gt; pretty useful. It&amp;rsquo;s a bare bones site, but
is not restricted to three tests a day the way mail-tester is. They also have
SPF and DomainKeys tests, but I didn&amp;rsquo;t need them. For a more general test
(everything, not just DKIM), I used &lt;a href=&#34;https://www.gmass.co/analyze&#34;&gt;https://www.gmass.co/analyze&lt;/a&gt;, which also
avoids the mail-tester restrictions. Eventually though, you really need to test
with &lt;a href=&#34;https://www.mail-tester.com&#34;&gt;https://www.mail-tester.com&lt;/a&gt;, because that seems to be the gold standard
for determining proper setup. If you&amp;rsquo;re scoring 10/10 on that site, which you
really should be, and your email isn&amp;rsquo;t going through, the problem is them, not
you.&lt;/p&gt;
&lt;p&gt;All inbound email is managed via MX records for the domain. I use
&lt;a href=&#34;https://www.cloudflare.com&#34;&gt;Cloudflare&lt;/a&gt; for that.&lt;/p&gt;
&lt;p&gt;Georgedatacenter, as mentioned above, has been good so far. Debian and docker
provide the remaining software stack.&lt;/p&gt;
&lt;h2 id=&#34;results&#34;&gt;Results&lt;/h2&gt;
&lt;p&gt;After testing this process thoroughly, I&amp;rsquo;ve moved the lerch.org mail to use this
set up. Now I have docker-mailserver handling my own email (inbound and outbound),
and push the rest of my family off to gmail. If the server goes down for some
reason, the fall back MX (mail server, mail exchange), remains gmail, so they
will pick it up. Any email I try to send will fail until I reconfigure or bring
my server up, but that&amp;rsquo;s ok by me.&lt;/p&gt;
&lt;p&gt;It is interesting to see the connections from various servers coming through my
new mail server. There sure is a fair amount of spam. But so far (only a couple
hours, granted), I&amp;rsquo;ve yet to see any spam get to my inbox. I&amp;rsquo;ll post an update
if this all turns out to be a terrible idea, but so far I&amp;rsquo;m looking at all this
advice on the Internet &amp;ldquo;you don&amp;rsquo;t want to host your own mail server&amp;rdquo; and saying,
&amp;ldquo;really? This doesn&amp;rsquo;t seem too bad.&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve followed along this far, I assume you&amp;rsquo;re curious, so here&amp;rsquo;s a full
diagram of my email setup. I still have some fine-tuning to do, but I&amp;rsquo;m pretty
happy with it. One note - another piece of advice you see a lot is to use
&lt;a href=&#34;https://notmuchmail.org/&#34;&gt;notmuch&lt;/a&gt; to allow easier searching. I find this
less than optimal for two reasons. 1) your inbox in (neo)mutt becomes effectively
read only, and 2) the search index is a massive single file, so synchronizing it
is a bit painful. I instead 95% rely on (neo)mutt&amp;rsquo;s built-in search, which is
nice and fast if you stick to what&amp;rsquo;s in subject or headers (just from?), and
use a special command that performs a notmuch-based search on my server if
I need something fancy. The server index is kept up to date as email comes in.&lt;/p&gt;



&lt;div class=&#34;goat svg-container &#34;&gt;
  
    &lt;svg
      xmlns=&#34;http://www.w3.org/2000/svg&#34;
      font-family=&#34;Menlo,Lucida Console,monospace&#34;
      
        viewBox=&#34;0 0 784 441&#34;
      &gt;
      &lt;g transform=&#39;translate(8,16)&#39;&gt;
&lt;path d=&#39;M 80,0 L 336,0&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 344,16 L 632,16&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 0,32 L 72,32&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 344,48 L 560,48&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 80,64 L 208,64&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 208,64 L 336,64&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 32,112 L 144,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 144,112 L 160,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 32,160 L 144,160&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 144,160 L 160,160&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 120,208 L 304,208&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 536,208 L 560,208&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 560,208 L 664,208&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 0,240 L 112,240&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 312,240 L 528,240&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 120,272 L 304,272&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 536,272 L 664,272&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 496,320 L 560,320&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 640,320 L 712,320&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 448,368 L 544,368&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 560,368 L 656,368&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 672,368 L 768,368&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 448,416 L 544,416&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 560,416 L 656,416&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 672,416 L 768,416&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 32,112 L 32,160&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 80,0 L 80,64&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 120,208 L 120,272&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 144,80 L 144,112&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 144,160 L 144,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 160,112 L 160,160&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 208,64 L 208,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 304,208 L 304,272&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 336,0 L 336,64&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 448,368 L 448,416&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 496,320 L 496,352&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 536,208 L 536,272&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 544,368 L 544,416&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 560,48 L 560,208&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 560,288 L 560,320&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 560,368 L 560,416&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 600,288 L 600,352&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 632,16 L 632,192&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 640,288 L 640,320&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 656,368 L 656,416&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 664,208 L 664,272&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 672,368 L 672,416&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 712,320 L 712,352&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;path d=&#39;M 768,368 L 768,416&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;80.000000,32.000000 68.000000,26.400000 68.000000,37.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 72.000000, 32.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;120.000000,240.000000 108.000000,234.399994 108.000000,245.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 112.000000, 240.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 144,72 L 144,80&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;160.000000,80.000000 148.000000,74.400002 148.000000,85.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(270.000000, 144.000000, 80.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 144,192 L 144,200&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;160.000000,192.000000 148.000000,186.399994 148.000000,197.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(90.000000, 144.000000, 192.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 208,192 L 208,200&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;224.000000,192.000000 212.000000,186.399994 212.000000,197.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(90.000000, 208.000000, 192.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;320.000000,240.000000 308.000000,234.399994 308.000000,245.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(180.000000, 312.000000, 240.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;352.000000,16.000000 340.000000,10.400000 340.000000,21.600000&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(180.000000, 344.000000, 16.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;352.000000,48.000000 340.000000,42.400002 340.000000,53.599998&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(180.000000, 344.000000, 48.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 496,352 L 496,360&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;512.000000,352.000000 500.000000,346.399994 500.000000,357.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(90.000000, 496.000000, 352.000000)&#39;&gt;&lt;/polygon&gt;
&lt;polygon points=&#39;536.000000,240.000000 524.000000,234.399994 524.000000,245.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(0.000000, 528.000000, 240.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 560,280 L 560,288&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;576.000000,288.000000 564.000000,282.399994 564.000000,293.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(270.000000, 560.000000, 288.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 600,280 L 600,288&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;616.000000,288.000000 604.000000,282.399994 604.000000,293.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(270.000000, 600.000000, 288.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 600,352 L 600,360&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;616.000000,352.000000 604.000000,346.399994 604.000000,357.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(90.000000, 600.000000, 352.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 632,192 L 632,200&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;648.000000,192.000000 636.000000,186.399994 636.000000,197.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(90.000000, 632.000000, 192.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 640,280 L 640,288&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;656.000000,288.000000 644.000000,282.399994 644.000000,293.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(270.000000, 640.000000, 288.000000)&#39;&gt;&lt;/polygon&gt;
&lt;path d=&#39;M 712,352 L 712,360&#39; fill=&#39;none&#39; stroke=&#39;currentColor&#39;&gt;&lt;/path&gt;
&lt;polygon points=&#39;728.000000,352.000000 716.000000,346.399994 716.000000,357.600006&#39; fill=&#39;currentColor&#39; transform=&#39;rotate(90.000000, 712.000000, 352.000000)&#39;&gt;&lt;/polygon&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;0&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;P&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;8&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;8&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;G&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;8&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;P&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;8&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;s&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;16&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;16&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;16&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;16&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;24&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;24&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;24&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;24&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;32&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;32&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;32&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;32&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;v&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;40&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;40&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;40&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;40&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;20&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;y&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;(&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;48&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;56&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;56&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;I&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;56&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;56&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;y&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;64&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;b&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;64&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;64&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;64&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;72&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;l&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;f&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;80&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;l&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;88&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;v&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;96&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;96&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;96&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;X&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;D&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;l&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;104&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;112&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;112&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;112&#39; y=&#39;276&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;l&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;120&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;P&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;120&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;v&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;120&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;128&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;128&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;128&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;136&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;136&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;136&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;136&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;144&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;144&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;144&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;)&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;144&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;X&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;152&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;160&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;160&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;P&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;168&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;168&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;176&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;y&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;176&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;184&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;192&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;0&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;192&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;200&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;:&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;200&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;208&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;216&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;216&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;E&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;216&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;216&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;y&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;224&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;y&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;224&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;224&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;h&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;232&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;232&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;232&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;1&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;240&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;240&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;240&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;240&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;:&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;248&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;248&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;l&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;256&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;256&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;&#34;&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;256&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;G&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;264&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;l&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;264&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;264&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;264&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;272&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;272&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;272&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;280&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;s&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;280&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;h&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;280&#39; y=&#39;148&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;&#34;&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;280&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;288&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;288&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;288&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;l&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;296&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;296&#39; y=&#39;132&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;304&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;v&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;312&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;320&#39; y=&#39;36&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;368&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;I&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;376&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;384&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;A&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;392&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;P&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;400&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;S&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;408&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;M&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;408&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;(&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;416&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;T&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;416&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;424&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;P&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;424&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;b&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;432&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;s&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;440&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;(&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;440&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;y&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;448&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;S&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;448&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;448&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;456&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;y&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;456&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;s&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;456&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;464&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;464&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;464&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;)&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;464&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;(&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;472&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;472&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;472&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;D&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;472&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;480&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;480&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;480&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;480&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;488&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;h&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;488&#39; y=&#39;68&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;)&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;488&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;v&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;488&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;496&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;496&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;496&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;504&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;504&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;504&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;512&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;g&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;512&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;512&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;520&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;528&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;(&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;528&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;)&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;536&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;544&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;544&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;(&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;544&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;552&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;552&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;g&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;552&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;560&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;l&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;560&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;H&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;560&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;560&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;568&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;d&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;568&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;568&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;-&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;568&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;576&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;576&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;576&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;576&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;576&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;(&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;584&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;584&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;584&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;584&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;584&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;D&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;584&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;592&#39; y=&#39;4&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;)&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;592&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;a&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;592&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;h&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;592&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;592&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;600&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;S&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;600&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;600&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;,&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;600&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;v&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;600&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;608&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;608&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;608&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;608&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;608&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;616&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;616&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;616&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;s&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;616&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;616&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;624&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;v&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;624&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;624&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;624&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;624&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;632&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;632&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;632&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;632&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;640&#39; y=&#39;228&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;r&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;640&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;f&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;640&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;p&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;640&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;)&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;648&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;y&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;648&#39; y=&#39;260&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;)&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;656&#39; y=&#39;244&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;,&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;672&#39; y=&#39;308&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;S&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;680&#39; y=&#39;308&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;y&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;688&#39; y=&#39;308&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;688&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;(&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;696&#39; y=&#39;308&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;696&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;D&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;696&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;704&#39; y=&#39;308&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;704&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;704&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;712&#39; y=&#39;308&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;h&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;712&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;v&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;712&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;o&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;720&#39; y=&#39;308&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;720&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;i&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;720&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;m&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;728&#39; y=&#39;308&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;n&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;728&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;c&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;728&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;u&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;736&#39; y=&#39;308&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;g&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;736&#39; y=&#39;388&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;e&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;736&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;744&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;t&lt;/text&gt;
&lt;text text-anchor=&#39;middle&#39; x=&#39;752&#39; y=&#39;404&#39; fill=&#39;currentColor&#39; style=&#39;font-size:1em&#39;&gt;)&lt;/text&gt;
&lt;/g&gt;

    &lt;/svg&gt;
  
&lt;/div&gt;
</description>
    </item>
    
    <item>
      <title>Zig build, and how I created a &#34;universal lambda&#34; function</title>
      <link>https://emil.lerch.org/zig-build-and-how-i-created-a-universal-lambda-function/</link>
      <pubDate>Sat, 21 Oct 2023 00:00:00 +0000</pubDate>
      
      <guid>https://emil.lerch.org/zig-build-and-how-i-created-a-universal-lambda-function/</guid>
      <description>&lt;h1 id=&#34;zig-build-overview-for-zig-0110-and-how-i-created-a-universal-lambda-function&#34;&gt;&lt;code&gt;zig build&lt;/code&gt; overview for zig 0.11.0, and how I created a &amp;ldquo;universal lambda&amp;rdquo; function&lt;/h1&gt;
&lt;p&gt;Having worked with zig starting in 0.9.0, the language, compiler, and build system
have come a long way. While still lacking documentation and stability, it feels
productive, and with the 0.11.0 release, I have stopped reaching for the latest
master branch builds.&lt;/p&gt;
&lt;p&gt;One of the properties of Zig, for both good and bad, is that it contains everything
needed for development. That means that while Zig is a language, it is also
a build system, and with 0.11.0, a package manager. In fact, some people ignore
the language entirely, with the exception of what is needed to build using zig.
Lacking documentation however, it has taken me a while to really get in depth
in the build system. I sense others are in a similar position. This, therefore,
is my attempt to provide an overview.&lt;/p&gt;
&lt;p&gt;Note that this post covers a snapshot in time, and some areas of the build, and
in particular details I will cover regarding the MVP package manager features,
will change significantly over the next few releases.&lt;/p&gt;
&lt;h2 id=&#34;terminology&#34;&gt;Terminology&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with some terminology, which has changed in 0.11.0. Quoting from
&lt;a href=&#34;https://github.com/ziglang/zig/issues/14307&#34;&gt;Issue 14307&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Package: A directory of files, uniquely identified by a hash of all files. Packages can export any number of compilation artifacts and modules&lt;/li&gt;
&lt;li&gt;Dependency: A directed edge between packages. A package may depend on any number of packages. A package may be a dependency of any number of packages&lt;/li&gt;
&lt;li&gt;Module: A directory of files, along with a root source file that identifies the file referred to when the module is used with @import&lt;/li&gt;
&lt;li&gt;Compilation artifact: a static library, a dynamic library, an executable, or an object file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You&amp;rsquo;ll note that the word &amp;ldquo;package&amp;rdquo; and &amp;ldquo;module&amp;rdquo; in particular are very similar.
A package has uses a hash for caching/download purposes. A module, however, is
really interested on what you can import with the &lt;code&gt;@import&lt;/code&gt; keyword. This will
remain confusing when using 0.11.0, because even if you understand the difference,
the terminology has not been fully updated in the standard library.&lt;/p&gt;
&lt;h2 id=&#34;what-happens-at-zig-build&#34;&gt;What happens at &lt;code&gt;zig build&lt;/code&gt;?&lt;/h2&gt;
&lt;p&gt;With terminology behind us, let&amp;rsquo;s cover the mechanics of the &lt;code&gt;zig build&lt;/code&gt; command.
Conceptually, the following process is followed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;build.zig.zon&lt;/code&gt; file is read. Packages described in the file will be fetched
if they do not exist in cache.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;build&lt;/code&gt; executable (literally named &amp;ldquo;build&amp;rdquo;) will be compiled&lt;/li&gt;
&lt;li&gt;The build command will be run. The command will create a directed acyclic graph
of &amp;ldquo;Steps&amp;rdquo; that must be executed to accomplish the end result(s). The steps
will then be executed in what is termed a &amp;ldquo;make phase&amp;rdquo;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This means that zig code is run at three stages in the build:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Compile time code during the compilation of the &lt;code&gt;build&lt;/code&gt; executable&lt;/li&gt;
&lt;li&gt;Run time code during the &amp;ldquo;build phase&amp;rdquo; while running the &lt;code&gt;build&lt;/code&gt; executable&lt;/li&gt;
&lt;li&gt;Run time code during the &amp;ldquo;make phase&amp;rdquo; while running the &lt;code&gt;build&lt;/code&gt; executable&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;step-1-package-fetch&#34;&gt;Step 1: Package fetch&lt;/h3&gt;
&lt;p&gt;Currently, step 1 includes package fetch. This process reads &lt;code&gt;build.zig.zon&lt;/code&gt;,
and I do not currently believe there is a way to override this name. Currently,
this process eagerly fetches all URLs embedded in this file, then applies
a hash algorithm to all files unpacked. We are in the MVP Package Manager
territory here, so things are rather fragile. For instance, &lt;a href=&#34;https://git.lerch.org/lobo/aws-sdk-for-zig/commit/eb91d40edf9ebb7b47d8fd958762b0341a6be2b6&#34;&gt;tarballs support
a limited subset of capabilities&lt;/a&gt;,
and the internal file layout is &lt;a href=&#34;https://github.com/ziglang/zig/blob/0.11.x/src/Package.zig#L696&#34;&gt;hard coded to strip 1 level of directories&lt;/a&gt;.
If you&amp;rsquo;re using GitHub, this is just fine. For those pushing the limits, be
aware of these limitations, and especially that most of these internals are
likely to change.&lt;/p&gt;
&lt;h3 id=&#34;step-1-package-fetchthe-big-lie&#34;&gt;Step 1: Package fetch&amp;hellip;the big lie&lt;/h3&gt;
&lt;p&gt;Packages are packages, but with &lt;code&gt;build.zig.zon&lt;/code&gt;, they are not just packages. They
also become modules. The package name defined in &lt;code&gt;build.zig.zon&lt;/code&gt; also becomes
a magical module with a root source file identified as &lt;code&gt;build.zig&lt;/code&gt;. This can
cause confusion on terminology, but is also &lt;strong&gt;intended&lt;/strong&gt; and &lt;strong&gt;super handy&lt;/strong&gt;.
It is also super confusing, because if I have a dependent package in
&lt;code&gt;build.zig.zon&lt;/code&gt; called &amp;ldquo;dep&amp;rdquo;, and that package exports a module called &amp;ldquo;dep&amp;rdquo;,
I can &lt;code&gt;@import(&amp;quot;dep&amp;quot;)&lt;/code&gt; in my &lt;code&gt;build.zig&lt;/code&gt;, but unless I call &lt;code&gt;exe.addModule&lt;/code&gt; or
&lt;code&gt;lib.addModule&lt;/code&gt; in my build.zig, the same &lt;code&gt;@import(&amp;quot;dep&amp;quot;)&lt;/code&gt; in my actual code
will fail. This is super-obvious if and only if you understand pretty much everything
I&amp;rsquo;ve written above this paragraph.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As a side note, packages are stored in the global zig cache. On Linux, this
is, by default, &lt;code&gt;$HOME/.cache/zig&lt;/code&gt;. Inside that directory, a directory named
simply &amp;ldquo;p&amp;rdquo; holds the packages. In there, you will see directories named by the
hash that matches &lt;code&gt;build.zig.zon&lt;/code&gt;. The reason all this is important, is that
once in the cache directory, the hash is not verified again. This enables
us to go muck around with the files in there without penalty&amp;hellip;a very handy
fact to know if you&amp;rsquo;re debugging some package&amp;rsquo;s behavior when it is used
in a consuming application!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Because there is no way in the build itself (step 2 of our &lt;code&gt;zig build&lt;/code&gt; procedure)
to use a module, we are given a way here to access our dependency, or at least
the &lt;code&gt;build.zig&lt;/code&gt; of a dependency. This allows build time access to our packages,
which we can then leverage to do such things as &amp;ldquo;reference a dependency, and
use their build helpers to tweak or reconfigure our own build&amp;rdquo;. This enables
some powerful capabilities, as I will show in my example below.&lt;/p&gt;
&lt;h3 id=&#34;step-2-compiling-the-build-executable&#34;&gt;Step 2: Compiling the &lt;code&gt;build&lt;/code&gt; executable&lt;/h3&gt;
&lt;p&gt;What&amp;rsquo;s in &lt;code&gt;build.zig&lt;/code&gt; is pretty boilerplate. However, there is no &lt;code&gt;main()&lt;/code&gt;
function. If this is an executable, where is &lt;code&gt;main()&lt;/code&gt;? This could be hard coded
in the compiler, but zig finds a different way. You can technically override
this with command line flags, though you &lt;a href=&#34;https://github.com/zigtools/zls/issues/1020&#34;&gt;don&amp;rsquo;t&lt;/a&gt;
&lt;a href=&#34;https://github.com/search?q=repo%3Azigtools%2Fzls+build+runner&amp;amp;type=issues&#34;&gt;want to&lt;/a&gt;.
Running &lt;code&gt;zig build&lt;/code&gt; will create a build with the main entrypoint defined in
&lt;a href=&#34;https://github.com/ziglang/zig/blob/0.11.x/lib/build_runner.zig&#34;&gt;build_runner.zig&lt;/a&gt;,
which subsequently references your own &lt;code&gt;build.zig&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Because zig code always has comptime also as a possibility, we end up with 1
comptime execution and 2 runtime passes logically (as described below). As an
aside, comptime in zig must support all compilation targets. Zig does not want
compilation to be OS or Processor specific!&lt;/p&gt;
&lt;h3 id=&#34;step-3-running-the-build-executable&#34;&gt;Step 3: Running the &lt;code&gt;build&lt;/code&gt; executable.&lt;/h3&gt;
&lt;p&gt;With the executable compiled, the last step is to run the build. Ultimately,
this executes the code in &lt;code&gt;build.zig&lt;/code&gt;. This code doesn&amp;rsquo;t actually do anything
tangible, however. What it does do is provide a dependency graph, to let the build
know that when a user provides a target/goal/step of, for instance &lt;code&gt;run&lt;/code&gt;, we
should find that run step, then back track to determine all the prerequisite
steps needed (and in what order) to make the end goal work. Let&amp;rsquo;s look at a
quick example. To follow along at home, simply &lt;code&gt;zig init-exe&lt;/code&gt; and look closely
at &lt;code&gt;build.zig&lt;/code&gt;, lines 32-53:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// This *creates* a Run step in the build graph, to be executed when another
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// step is evaluated that depends on it. The next line below will establish
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// such a dependency.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; run_cmd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; b.addRunArtifact(exe);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// By making the run step depend on the install step, it will be run from the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// installation directory rather than directly from within the cache directory.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// This is not necessary, however, if the application depends on other installed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// files, this ensures they will be present and in the expected location.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    run_cmd.step.dependOn(b.getInstallStep());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// This allows the user to pass arguments to the application in the build
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// command itself, like this: `zig build run -- arg1 arg2 etc`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (b.args) &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt;args&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        run_cmd.addArgs(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// This creates a build step. It will be visible in the `zig build --help` menu,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// and can be selected like this: `zig build run`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// This will evaluate the `run` step rather than the default, which is &amp;#34;install&amp;#34;.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; run_step &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; b.step(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;run&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Run the app&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    run_step.dependOn(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;run_cmd.step);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The comments are added by &lt;code&gt;zig init-exe&lt;/code&gt;, but if you&amp;rsquo;re trying to get some
stuff done, you may not have taken the time to really think about it. Let&amp;rsquo;s
start from the top. &lt;code&gt;zig build&lt;/code&gt; does not have any step targets. So, it will run
the &amp;ldquo;install step&amp;rdquo; by default. It is the same as &lt;code&gt;zig build install&lt;/code&gt;. You can
actually put as many build steps as you&amp;rsquo;d like on the command line. &lt;code&gt;zig build install uninstall run test&lt;/code&gt; is perfectly valid. What we show above, though, is
the code for the run step. The run step itself does nothing put put &amp;lsquo;run&amp;rsquo; on
the help menu and makes it a valid target. Again, this &lt;em&gt;&lt;strong&gt;does nothing&lt;/strong&gt;&lt;/em&gt;. What
actually makes it do something, is the very last line of code. That line of
code connects the command line step to the command that actually runs the code.
The code can&amp;rsquo;t run without the code being built, so we see that
&lt;code&gt;run_cmd.step.dependOn(b.getInstallStep())&lt;/code&gt; to let the build system know that
a) first we install (compile and put the binary in the output directory), b)
then we run, then c) we &amp;ldquo;do nothing&amp;rdquo; but complete the run step.&lt;/p&gt;
&lt;p&gt;Only after the graph has been completed, the build will determine (starting
from the command line), which steps the user is requesting, and will use the
graph to define an ordering. With zig 0.11.0, steps can now execute in
parallel. The actual execution of these steps is considered the &lt;code&gt;make phase&lt;/code&gt;
and runs the required steps, in order, based on the user input. As an example,
&lt;code&gt;zig build&lt;/code&gt; implies the &amp;ldquo;install step&amp;rdquo;. But a common command would be &lt;code&gt;zig build run&lt;/code&gt;, that has at its goal to perform the &lt;code&gt;run step&lt;/code&gt; (which depends on
the install step). Again, a user is &lt;strong&gt;not&lt;/strong&gt; limited to a single end goal, so
you can, for instance, specify &lt;code&gt;zig build test run&lt;/code&gt; on the command line.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re doing a lot of fancy build stuff, one frustration might be the use
(or lack thereof) of modules in the build phase. The lib/exe &lt;code&gt;addModule&lt;/code&gt;
functions are for the compilation of the target code, not for the build itself!
This is mostly ok, as a package has a module available at build (via build.zig),
so defining a &lt;code&gt;pub fn&lt;/code&gt; in build.zig can then reference whatever you want.
However, it currently limits chained dependencies, so an &lt;code&gt;@import&lt;/code&gt; of &lt;code&gt;dep-of-dep&lt;/code&gt;
in package &lt;code&gt;dep&lt;/code&gt; is not possible in the main project unless &lt;code&gt;dep-of-dep&lt;/code&gt; is also
in the main project&amp;rsquo;s &lt;code&gt;build.zig.zon&lt;/code&gt;. I am fairly confident this will be fixed
(maybe while introducing other problems?) in zig 0.12.0.&lt;/p&gt;
&lt;h2 id=&#34;an-example-a-universal-lambda-function&#34;&gt;An example: A &amp;ldquo;universal lambda function&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;I have created a package &lt;a href=&#34;https://git.lerch.org/lobo/universal-lambda-zig&#34;&gt;universal-lambda-zig&lt;/a&gt;
that leverages these features to allow minimal changes to a typical console
&amp;ldquo;hello world&amp;rdquo; that can be run in multiple environments. This includes all
zig targets, including WASM/WASI, AWS Lambda, my own home grown web server,
and Cloudflare. These environments are all wildly different from each
other. Consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;console exes run &lt;code&gt;main()&lt;/code&gt; front to back, then exit&lt;/li&gt;
&lt;li&gt;WASM/WASI do the same as above, but are highly sandboxed, and have no threading&lt;/li&gt;
&lt;li&gt;AWS Lambda runs functions based on a bootstrap executable that, when run,
makes a web request to a server that issues instructions. In the universal
lambda implementation, this bootstrap process calls the client code in process
(no &lt;code&gt;exec&lt;/code&gt; calls).&lt;/li&gt;
&lt;li&gt;My own web server relies on discovering a function in a dynamic library, then
calling it&lt;/li&gt;
&lt;li&gt;Cloudflare relies on a JavaScript wrapper that calls a WASM/WASI file and
marshalls the appropriate input/output/arguments/environment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By adding a reference into &lt;code&gt;build.zig.zon&lt;/code&gt;, then calling a &amp;ldquo;configureBuild&amp;rdquo;
function, the universal lambda project has a view to the build process, can add
modules, etc. From the application&amp;rsquo;s main source file (by default &lt;code&gt;src/main.zig&lt;/code&gt;),
you can access the universal lambda module and register your handler. From there,
the package and module can take over:&lt;/p&gt;
&lt;h3 id=&#34;console-application-and-wasmwasi&#34;&gt;Console application and WASM/WASI&lt;/h3&gt;
&lt;p&gt;All the universal lambda package needs to do here is get pass control back
to the application. The build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adds modules for use by the application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The module:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reads standard input&lt;/li&gt;
&lt;li&gt;Calls the handler&lt;/li&gt;
&lt;li&gt;Writes handler output to standard output&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;WASM/WASI is just slightly different, because the module won&amp;rsquo;t compile by default
due to the fact that &lt;code&gt;std.http.Server.Response&lt;/code&gt; is used in the event handler
context object. A little comptime magic is used to escape that problem.&lt;/p&gt;
&lt;h3 id=&#34;aws-lambda&#34;&gt;AWS Lambda&lt;/h3&gt;
&lt;p&gt;The design of AWS Lambda is pretty unique. Here, we need to take the application&amp;rsquo;s
handler and bundle it with an http client to deal with all the Lambda internals.
To me, the service seems over-engineered, but I&amp;rsquo;m not on that team and I&amp;rsquo;m sure
they have good reasons for all that. Anyway, this was my original goal, so it&amp;rsquo;s
still full of hacks and learnings. The biggest of which is that the build portion
is actually using the AWS CLI and not making good use of the make phase, at all.
There are current package manager limitations with transitive dependencies that
are preventing me from putting this into the ideal state. Also, AWS doesn&amp;rsquo;t yet
universally support TLS 1.3, which is the only secure protocol supported by the
http client in zig 0.11.0. I plan to re-address this with zig 0.12.0, by which
time I believe both issues will be fixed. In the meantime, it&amp;rsquo;s hacky, relies
on the AWS CLI, and is coded to not even show the options if you are running
anything other than Linux (other POSIX OS&amp;rsquo;s like mac, *BSD, etc should work,
but haven&amp;rsquo;t been tested). The build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adds modules for use by the application&lt;/li&gt;
&lt;li&gt;Sets up steps to package, set up permissions, deploy, and run the function
using &lt;code&gt;addSystemCommand&lt;/code&gt; which creates steps in the graph to run system
commands. This is how we get to the AWS CLI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fundamentally, in lambda, &lt;code&gt;bootstrap&lt;/code&gt; is still a command line executable, so
we don&amp;rsquo;t need to do any magic there, but we do need to insert a whole http thing&amp;hellip;&lt;/p&gt;
&lt;p&gt;The module:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;run&lt;/code&gt; in the module, which is called by the application, becomes 500 lines
of code and tests designed to integrate our application to the AWS Lambda system.
It spins up a loop, requesting data from a web server (endpoint is passed
via environment variables). Full docs are here: &lt;a href=&#34;https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html&#34;&gt;https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;When data is returned from Lambda&amp;rsquo;s web server, the module passes that data as
event data to the application&amp;rsquo;s handler, and provide the raw response object
as context&lt;/li&gt;
&lt;li&gt;After the handler returns, we take the response and post it to the appropriate
Lambda endpoint&lt;/li&gt;
&lt;li&gt;Errors are caught and similarly posted to the Lambda error endpoint&lt;/li&gt;
&lt;li&gt;Loop continues&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;flexilib&#34;&gt;Flexilib&lt;/h3&gt;
&lt;p&gt;The design of my personal web server is to allow a set of independently developed
(but trusted) projects to plug in as a single process. The goal is to eliminate
managing lots of processes and their associated memory requirements by allowing
me to slap together a bunch of disparate microservices and run them together.
There is no assumption of security boundaries within the system. It is similar to,
but more flexible than, how Redis handles modules. This is a little different,
because in this case, our package needs to modify the build much more
substantially. The build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adds modules for use by the application&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Changes&lt;/strong&gt;&lt;/em&gt; the install output from an executable to a shared library
(note - I think this is a mistake, and ultimately the package should probably
add an &lt;strong&gt;additional&lt;/strong&gt; build artifact. This would allow things like &lt;code&gt;zig build run flexilib&lt;/code&gt; to work properly)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Changes&lt;/strong&gt;&lt;/em&gt; the root source file for the build to its own flexilib.zig&lt;/li&gt;
&lt;li&gt;Adds a module for the application code. flexilib.zig can then utilize that module&lt;/li&gt;
&lt;li&gt;Adds code to be compiled into the library. The code exports the necessary
flexilib functions. Those functions handle marshalling into more zig-friendly
data formats (flexilib uses C calling conventions)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The module:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Doesn&amp;rsquo;t truly exist in the traditional manner at the moment. It really
doesn&amp;rsquo;t do anything other than exist to allow the build to work&lt;/li&gt;
&lt;li&gt;The request handler ignores the application&amp;rsquo;s main function and just looks
for a function named &amp;ldquo;handler&amp;rdquo;. I think this could be fixed by providing a
run function, calling the application&amp;rsquo;s main function on first use, then
utilizing our own run function to simply set a variable for the handler.
But this remains a TODO for now.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;cloudflare&#34;&gt;Cloudflare&lt;/h3&gt;
&lt;p&gt;There are a couple options for Cloudflare worker support. Cloudflare workers
operate in V8 sandboxes (called &amp;ldquo;isolates&amp;rdquo;). Since we&amp;rsquo;re in V8, we&amp;rsquo;re firmly
in web technology land. This gives us the following options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compile zig to Javascript. I don&amp;rsquo;t know if this has been done, but it seems&amp;hellip;rough&lt;/li&gt;
&lt;li&gt;Compile zig to wasm, provide a Javascript wrapper to wasm functions, and run
the handler through this infrastructure. This&amp;hellip;seemed interesting and I did
consider it. Fundamentally, it&amp;rsquo;s not too different from what we&amp;rsquo;re doing with
Flexilib above. Compile the code as a library, expose the application&amp;rsquo;s handler
as a wasm export, then put some JS goo around it.&lt;/li&gt;
&lt;li&gt;Compile zig to wasm/wasi, and use Cloudflare&amp;rsquo;s experimental wasi support
to run it. This option was ultimately chosen, as it was much more straightforward.
The main drawback here is the experimental tag.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the option chosen, the implementation was nearly identical to just a straight
WASM/WASI compile from our first foray above. The primary changes were in the
build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adds modules for use by the application&lt;/li&gt;
&lt;li&gt;Adds a Cloudflare deploy step to the dependency graph, which:
&lt;ul&gt;
&lt;li&gt;Modifies a built-in wrapper Javascript &lt;code&gt;index.js&lt;/code&gt; to refer to the
build output&amp;rsquo;s wasm file&lt;/li&gt;
&lt;li&gt;Combines &lt;code&gt;index.js&lt;/code&gt; into another wrapper that has the necessary wasi
interface goo (leveraged from Cloudflare&amp;rsquo;s Wrangler and cloudflare wasi
projects)&lt;/li&gt;
&lt;li&gt;Determines the appropriate Cloudflare account&lt;/li&gt;
&lt;li&gt;Uploads the worker, along with &lt;code&gt;memfs.wasm&lt;/code&gt;, needed by Cloudflare&amp;rsquo;s wrapper
and the build output wasm file to Cloudflare&lt;/li&gt;
&lt;li&gt;Enables the worker if needed (only necessary if the worker is being created)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This took quite a bit of work, but was straightforward in terms of a) understanding
the apis and interactions and b) understanding the structure of various open
source Cloudflare projects.&lt;/p&gt;
&lt;p&gt;The module is identical to the console application above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reads standard input&lt;/li&gt;
&lt;li&gt;Calls the handler&lt;/li&gt;
&lt;li&gt;Writes handler output to standard output&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;While the zig build system and package manager are powerful, there remain some
edge cases. I was pleasantly surprised how functional and flexible the system
was. In terms of &amp;ldquo;MVP&amp;rdquo;, I&amp;rsquo;d say that the bar has been hit. There are two
primary issues remaining:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Transitive dependencies, especially at build time. My Cloudflare build process
for example, really should be another package. The problem is that I can&amp;rsquo;t really
do a package of a package, at build time, without adding the dependency directly
to the application code. Ultimately, the application&amp;rsquo;s &lt;code&gt;build.zig.zon&lt;/code&gt; currently
requires a dependency for the flexilib interface, which is the result of this
issue and shouldn&amp;rsquo;t be needed. Zig 0.12.0 is addressing this&lt;/li&gt;
&lt;li&gt;Pathing problems. Adding the package&amp;rsquo;s module for use of the application
requires an &amp;ldquo;anonymous module&amp;rdquo;, with the source file residing in the package
just fetched. One could argue that this should be a recognized use case, and
it may be in the future. In the meantime, I need to know the location the
package&amp;rsquo;s files without relying on knowledge of the zig cache or the hash
of the package. Right now, there&amp;rsquo;s a bit of an ugly hack that triggers the
build system to initialize the correct dependency, then pokes into the file
system looking for a file we recognize in the dependency&amp;rsquo;s location. The
hack itself is a bit less hacky in 0.12.0, but the whole thing makes me want
to take a shower.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Outside these two issues, I find it amazing how much I was able to twist and
turn the build and runtime behavior from code outside the application. Quite an
MVP, and looking forward to 0.12.0!&lt;/p&gt;
&lt;p&gt;For further reading on zig build system, take a look at: &lt;a href=&#34;https://zig.news/edyu/zig-package-manager-wtf-is-zon-2-0110-update-1jo3&#34;&gt;https://zig.news/edyu/zig-package-manager-wtf-is-zon-2-0110-update-1jo3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The article takes a slightly different angle than I have here. I have also
heard that with 0.12.0, we are likely to get more documentation around the build
system. Definitely looking forward to that!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Wrapping up our exploration: A mini shell</title>
      <link>https://emil.lerch.org/wrapping-up-our-exploration-a-mini-shell/</link>
      <pubDate>Sun, 07 May 2023 00:00:00 +0000</pubDate>
      
      <guid>https://emil.lerch.org/wrapping-up-our-exploration-a-mini-shell/</guid>
      <description>&lt;h1 id=&#34;wrapping-up-our-exploration-a-mini-shell&#34;&gt;Wrapping up our exploration: A mini shell&lt;/h1&gt;
&lt;p&gt;This is part of an ongoing series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;./exploring-embedded-programming-with-sipeed-m0s-bl616/&#34;&gt;Part 1: Exploring embedded programming with the Sipeed M0S with the BL616 microprocessor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./getting-to-hello-world-with-sipeed-m0s-bl616/&#34;&gt;Part 2: Getting to hello world with Sipeed m0s (BL616)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./simplifying-our-tool-chain-first-steps/&#34;&gt;Part 3: Simplifying the tool chain: First steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./simplifying-our-tool-chain-wrap-up/&#34;&gt;Part 4: Simplifying the tool chain: Wrap up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./learning-the-sdk-and-usb-protocol//&#34;&gt;Part 5: Learning the SDK and USB protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 6: Wrapping up our exploration: A mini shell&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Through previous efforts, I now have two USB devices. One for debug messages,
and one for whatever we want. I don&amp;rsquo;t have any brilliant plans here&amp;hellip;this is
primarily an academic exercise to get knowledge in this world. So let&amp;rsquo;s put
together the beginnings of a shell to see how things work out. Our todo list
is nearly complete&amp;hellip;the main question remaining is &amp;ldquo;What is this SDK doing for
us? Can we do it ourselves?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The answer seems to be, &amp;ldquo;abstracting the hardware interface&amp;rdquo;. Through the Bouffalo
Labs SDK, we don&amp;rsquo;t need to worry about the memory mapped I/O addresses and
interrupts coming from various devices. I had expected to need to do some assembly
work, but as it turns out, using the SDK allows me to simply use C. The hardware
specifics are coded into the SDK, while in Linux many (but not all) of these
details are in the &lt;a href=&#34;https://www.devicetree.org/&#34;&gt;device tree&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Most of the hard work is done, so let&amp;rsquo;s create a shell! Since the last post,
I spent a bit of time on some niceties, implementing colorized debugging,
making indentation consistent, and removing unneeded code. One thing I haven&amp;rsquo;t
quite sorted out, however, is the use of &lt;code&gt;ep_tx_busy_flag&lt;/code&gt;. Waiting until
this flag is set to false seems to hang my interfaces. Not waiting, however,
will result in intermittent glitches. The glitches are fairly uncommon, however,
and this is just an example, so I&amp;rsquo;ve decided to ignore the problem for the moment.&lt;/p&gt;
&lt;p&gt;One software/hardware addition to the niceties, however, is to use system uptime
in the debugging logs. This proved interesting, as we can see the timing between
operations when, for instance, delays are added artificially. This is done through
&lt;code&gt;bflb_mtimer_get_time_ms()&lt;/code&gt;, which simply reads from the appropriate timer memory
address and converts microseconds to milliseconds.&lt;/p&gt;
&lt;p&gt;To keep things in &lt;code&gt;main.c&lt;/code&gt;, I created two function pointers in the newly renamed
&lt;code&gt;cdc_acm_usb_interface.c&lt;/code&gt;. One is to notify when the data terminal ready state
changes. The other, for when data is received. We&amp;rsquo;ll use these in &lt;code&gt;main.c&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First, we want to display a prompt when our data terminal is ready (here, through
&lt;code&gt;screen /dev/ttyACM0 2000000&lt;/code&gt;). This looks like the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;dtr_changed&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; dtr) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (dtr) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;debuglog&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;DTR enabled: requesting prompt&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    display_prompt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;board_init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;debuglog&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Initialized&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  dtr_changed_ptr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;dtr_changed;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  data_received_ptr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;data_received;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (display_prompt) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;/* We can&amp;#39;t display directly on the dtr_enabled interrupt, must be on the
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;       * main loop. Without any delay, we will not see a prompt. But even 1ms
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;       * is enough
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;       */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      display_prompt &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_mtimer_delay_ms&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt;(prompt);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;debuglog&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;displayed prompt&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      curr_char &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A couple interesting things here. At first, I tried to display the prompt directly
in &lt;code&gt;dtr_changed&lt;/code&gt;, but found that it just wasn&amp;rsquo;t possible, and nothing would
be displayed until further interaction occurred. I ultimately decided that
issuing I/O while in an interrupt handler was probably a bad idea, so I used
a &lt;code&gt;volatile&lt;/code&gt; flag &lt;code&gt;display_prompt&lt;/code&gt; to signal to the main function that we were
ready to go. Even there, I was unable to just display the prompt&amp;hellip;a small
delay was needed. At one millisecond, no one will notice an artificial display,
so while &lt;code&gt;bflb_mtime_delay_us&lt;/code&gt; is available, I chose not to use it. My debug
log on &lt;code&gt;/dev/ttyACM1&lt;/code&gt; was invaluable to this process.&lt;/p&gt;
&lt;p&gt;ok, so a prompt is now showing. What about processing actual commands? Here we
have a few tasks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We want to echo characters back to the terminal as they are typed&lt;/li&gt;
&lt;li&gt;We want to process a command when the &lt;code&gt;enter&lt;/code&gt; key is pressed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&amp;rsquo;s how that looks:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;data_received&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; nbytes, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;bytes) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/* I think we&amp;#39;re getting an SOH after our output, but not sure why exactly */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/* This if statement is a bit fragile (e.g. it doesn&amp;#39;t cover SOH + data) */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/* so we may need some further processing */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (curr_char &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; nbytes &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;bytes &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0x01&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/* if (nbytes == 1) */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/*   debuglog(&amp;#34;Received the letter &amp;#39;%c&amp;#39;. curr_char %d\r\n&amp;#34;, *bytes, curr_char); */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (curr_char &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; nbytes &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1024&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* We will overflow - bail */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;debugerror&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;command too long&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;COMMAND TOO LONG&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%s&amp;#34;&lt;/span&gt;, prompt);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    curr_char &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;/* Process new data */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;memcpy&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cmd_buffer[curr_char], bytes, nbytes);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;raw_output&lt;/span&gt;(nbytes, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cmd_buffer[curr_char]); &lt;span style=&#34;color:#75715e&#34;&gt;/* Echo data back to console */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (nbytes &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; cmd_buffer[curr_char] &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\r&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* User hit enter, process command */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_mtimer_delay_ms&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;/* There is a microsecond delay as well */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cmd_buffer[curr_char] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;\0&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;debuglog&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Processing command &amp;#39;%s&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cmd_buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;process_cmd&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cmd_buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], curr_char &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%s&amp;#34;&lt;/span&gt;, prompt);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    curr_char &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  curr_char &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; nbytes;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;data_received&lt;/code&gt; function is assigned to the function pointer provided in
our usb c file. I found that data was coming in prior to data terminal ready,
and I suspect that this was some setup data coming through. I could easily log
it with my debug functions at this point, but ultimately I just throw away
the data when displaying the prompt in &lt;code&gt;main&lt;/code&gt;. In this function we have few
guard clauses to make sure we don&amp;rsquo;t overrun our command buffer. Next, we copy
the incoming character into our buffer, then check to see if enter &lt;code&gt;\r&lt;/code&gt; has been
pressed.&lt;/p&gt;
&lt;p&gt;If it has, we need to swing into action. Pressing enter should move to the next
line, so we output the &lt;code&gt;\r\n&lt;/code&gt; sequence to make that happen. To avoid glitching,
we insert an artificial delay, then we set a null character at the end of the
command. This shouldn&amp;rsquo;t be necessary, and ultimately I shouldn&amp;rsquo;t be assuming
null termination (this is a buffer overrun exploit waiting to happen), but this
is sample code, so I&amp;rsquo;ve not dealt with that problem. The null terminator does
let me quickly log the command, which was helpful in development to track
down the extra characters before DTR was enabled. Lastly, we call the
&lt;code&gt;process_cmd&lt;/code&gt; function to do whatever it needs to do before outputting a new
prompt and resetting the index to our command buffer. Not too bad.&lt;/p&gt;
&lt;p&gt;To process the command, I only implemented a quick and dirty &lt;code&gt;echo&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;process_cmd&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;cmd, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; cmd_len){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; prefix_len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;strlen&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;echo &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;strncmp&lt;/span&gt;((&lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)cmd, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;echo &amp;#34;&lt;/span&gt;, prefix_len) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;raw_output&lt;/span&gt;(cmd_len &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; prefix_len &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, cmd &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; prefix_len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_mtimer_delay_ms&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;/* There is a microsecond delay as well */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;output&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is primarily to give a feel to how we could start an implementation of
multiple commands. Here we just look for &lt;code&gt;echo &lt;/code&gt; and output whatever comes
after, but we could just as easily read/write/list/delete files, start wifi,
perform encryption, or whatever we need right here.&lt;/p&gt;
&lt;p&gt;And with that, my exploration is complete, and my &amp;ldquo;final&amp;rdquo; code is here:
&lt;a href=&#34;https://github.com/elerch/bl616-usb-cdc-acm/tree/dd9bd918628b40b090eaa3396053e4726a6d3116&#34;&gt;https://github.com/elerch/bl616-usb-cdc-acm/tree/dd9bd918628b40b090eaa3396053e4726a6d3116&lt;/a&gt;
If I have something that calls for this neat little device, I may revisit, but
in the meantime, it&amp;rsquo;s been fun exploring modern embedded development.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Learning the SDK and USB protocol</title>
      <link>https://emil.lerch.org/learning-the-sdk-and-usb-protocol/</link>
      <pubDate>Mon, 01 May 2023 00:00:00 +0000</pubDate>
      
      <guid>https://emil.lerch.org/learning-the-sdk-and-usb-protocol/</guid>
      <description>&lt;h1 id=&#34;learning-the-sdk-and-usb-protocol&#34;&gt;Learning the SDK and USB protocol&lt;/h1&gt;
&lt;p&gt;This is part of an ongoing series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;./exploring-embedded-programming-with-sipeed-m0s-bl616/&#34;&gt;Part 1: Exploring embedded programming with the Sipeed M0S with the BL616 microprocessor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./getting-to-hello-world-with-sipeed-m0s-bl616/&#34;&gt;Part 2: Getting to hello world with Sipeed m0s (BL616)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./simplifying-our-tool-chain-first-steps/&#34;&gt;Part 3: Simplifying the tool chain: First steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./simplifying-our-tool-chain-wrap-up/&#34;&gt;Part 4: Simplifying the tool chain: Wrap up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 5: Learning the SDK and USB protocol&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./wrapping-up-our-exploration-a-mini-shell/&#34;&gt;Part 6: Wrapping up our exploration: A mini shell&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With our toolchain in place, it&amp;rsquo;s now time to actually do something real. In
part 2, we we were able to get a hello world over USB by largely copying and
pasting our way to glory. Now it&amp;rsquo;s time to actually &lt;strong&gt;understand&lt;/strong&gt; what we
copied and pasted, and augment it. Here&amp;rsquo;s our current todo list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What&amp;rsquo;s the difference between TinyUSB and CherryUSB?&lt;/li&gt;
&lt;li&gt;(optional) What is this library doing for us? Can we do it ourselves?&lt;/li&gt;
&lt;li&gt;What is going on in &lt;code&gt;cdc_acm_template.c&lt;/code&gt; Specifically what is the &lt;code&gt;dtr&lt;/code&gt; stuff?&lt;/li&gt;
&lt;li&gt;What is USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX?&lt;/li&gt;
&lt;li&gt;Figure out logging output destination&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And now that we&amp;rsquo;ve simplified our software stack, we&amp;rsquo;ll learn how to read data.
However, my personal philosophy is that &amp;ldquo;it&amp;rsquo;s hard to debug what you can&amp;rsquo;t see&amp;rdquo;,
so before reading data, let&amp;rsquo;s complete a bunch of our todo list and get some
debugging capabilities along the way. Right now, we have a single channel, and
I&amp;rsquo;d like to avoid using CPU debug features or JTag or anything&amp;hellip;I want to live
in the comfort of a single wire from my computer to the device. However, I know
from my webcam that a single physical device can provide multiple virtual
devices, so I should be able to do that. If I am able to provide &lt;code&gt;/dev/ttyACM0&lt;/code&gt;
and &lt;code&gt;/dev/ttyACM1&lt;/code&gt;, I can put debug information on one interface while my
program works on the other. I can even turn my debug interface into a full blown
&lt;a href=&#34;https://en.wikipedia.org/wiki/Control_plane&#34;&gt;control plane&lt;/a&gt;. But first things
first. My objective today - get two interfaces recognized by Linux.&lt;/p&gt;
&lt;h1 id=&#34;getting-two-interfaces&#34;&gt;Getting two interfaces&lt;/h1&gt;
&lt;p&gt;First, I&amp;rsquo;ll take a look at my meta files. We learned a lot about the project
structure, etc., in parts 2 and 3 of this exploration, so armed with that
knowledge, we can go back though each of the files in my project and make sure
they&amp;rsquo;re appropriate.  It looks like my &lt;code&gt;proj.conf&lt;/code&gt; has the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;set(CONFIG_CHERRYUSB 1)
set(CONFIG_CHERRYUSB_DEVICE 1)
set(CONFIG_CHERRYUSB_DEVICE_CDC 1)
set(CONFIG_CHERRYUSB_DEVICE_HID 1)
set(CONFIG_CHERRYUSB_DEVICE_MSC 1)
set(CONFIG_CHERRYUSB_DEVICE_AUDIO 1)
set(CONFIG_CHERRYUSB_DEVICE_VIDEO 1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What are those last 4 doing there? Certainly I&amp;rsquo;m using CherryUSB, and I&amp;rsquo;m
working with the device library, and the USB CDC class. But I&amp;rsquo;m not doing
anything with these other classes, so why are they being included? I feel pretty
strongly that this is not needed, so a quick removal of the HID line and beyond
is worth trying. We&amp;rsquo;ll remove those 4 lines, build, and flash and see if anything
breaks. As it turns out, all is good, so we have less code to deal with, and
a &lt;code&gt;proj.conf&lt;/code&gt; file that now reads simply:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;set(CONFIG_CHERRYUSB 1)
set(CONFIG_CHERRYUSB_DEVICE 1)
set(CONFIG_CHERRYUSB_DEVICE_CDC 1)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I also know that most of the USB setup work is in &lt;code&gt;cdc_acm_template.c&lt;/code&gt;. The
&lt;code&gt;template&lt;/code&gt; part of this tells me it&amp;rsquo;s time to go looking at &lt;a href=&#34;https://github.com/sakumisu/CherryUSB/&#34;&gt;CherryUSB&lt;/a&gt;.
In the demo directory are a bunch of other templates, including an interesting
one that seems to indicate support for &lt;a href=&#34;https://github.com/sakumisu/CherryUSB/blob/master/demo/cdc_acm_multi_template.c&#34;&gt;multiple interfaces&lt;/a&gt;.
It looks incomplete, but does tell me that this is possible. We&amp;rsquo;ll bookmark
this file for the time being, and seek to understand how USB handshake works.&lt;/p&gt;
&lt;p&gt;Along the way I learn an interesting tidbit that &lt;a href=&#34;https://wiki.wireshark.org/CaptureSetup/USB&#34;&gt;WireShark can sniff USB traffic
with the usbmon kernel module in Linux&lt;/a&gt;,
but this seems overkill for what I&amp;rsquo;m doing. All the setup in this template
keeps talking about descriptors, which leads me to start investigating USB
descriptors. I found that &lt;code&gt;lsusb&lt;/code&gt; has a verbose option to show the device
descriptors, which let&amp;rsquo;s me look at how my web cam (which provides &lt;code&gt;/dev/video0&lt;/code&gt;
and &lt;code&gt;/dev/video1&lt;/code&gt;) advertises itself.&lt;/p&gt;
&lt;p&gt;Eventually, armed with enough knowledge to search the Internet with the right
keywords, I search for &amp;lsquo;USB device desciptor handshake&amp;rsquo; and run across an
absolute &lt;a href=&#34;https://beyondlogic.org/usbnutshell/usb5.shtml&#34;&gt;gold mine of information&lt;/a&gt;.
Seriously. Go read that article right now and come back. It&amp;rsquo;s amazing. What
isn&amp;rsquo;t clear to me after reading the overview is &amp;ldquo;can one hardware device present
multiple USB devices (the root of the tree)?&amp;rdquo;, and if not, at what level are
multiple virtual devices presented to the OS? The problem here is terminology.
We speak about &amp;ldquo;USB devices&amp;rdquo; or maybe &amp;ldquo;USB virtual devices&amp;rdquo;, where in USB world,
&amp;ldquo;interfaces&amp;rdquo; are the thing. Through some research I&amp;rsquo;m lead to believe that
there is only one device descriptor for a physical device. Files in &lt;code&gt;/dev&lt;/code&gt;
(Linux device? Virtual device?) are based on interfaces. And as this is a
communication device, there are actually 3 interfaces involved, which we can
see by looking at &lt;code&gt;lsusb -v -d ffff:ffff&lt;/code&gt; or by checking the &lt;a href=&#34;https://github.com/sakumisu/CherryUSB/blob/master/class/cdc/usb_cdc.h#L371-L435&#34;&gt;source code of
usb_cdc.h in CherryUSB&lt;/a&gt;.
There is 1) an interface called an &amp;ldquo;interface association&amp;rdquo; to say, &amp;ldquo;hey, I have
this thing, and this thing has multiple interfaces&amp;rdquo;. This is what triggers
a file in &lt;code&gt;/dev&lt;/code&gt;, or what I&amp;rsquo;ll call a &amp;ldquo;Linux device&amp;rdquo;. Then, for what we&amp;rsquo;re doing,
we have 2 additional interfaces. The first of these is an interface for &amp;ldquo;inbound&amp;rdquo;
traffic (device to host), and the second for &amp;ldquo;outbound&amp;rdquo; traffic (host to device).
This seems a little surprising to me, because there are also endpoints, and it
seems as though one interface with two endpoints would be ok?&lt;/p&gt;
&lt;p&gt;Each interface has at least one endpoint, so for CDC ACM, we have an &amp;ldquo;IN&amp;rdquo; endpoint,
an &amp;ldquo;OUT&amp;rdquo; endpoint, each attached to their respective interfaces. We also have
an &amp;ldquo;INT&amp;rdquo; endpoint, which controls the device. As this USB device is designed
for things like modems, my assumption is that it is meant to be where one
would send the equivalent of AT commands. In any case, I don&amp;rsquo;t see a way to
work with that endpoint directly and haven&amp;rsquo;t bothered digging to find out&amp;hellip;it
is necessary but fairly irrelevant to the goal here.&lt;/p&gt;
&lt;p&gt;So, knowledge in hand, let&amp;rsquo;s run back to the source code of &lt;code&gt;cdc_acm_template.c&lt;/code&gt;.
I can see these lines at the top. Now that I&amp;rsquo;ve gone through the article, I
am confident these are my endpoint numbers:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define CDC_IN_EP  0x81
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define CDC_OUT_EP 0x02
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define CDC_INT_EP 0x85
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So it seems I can just duplicate these constants. Let&amp;rsquo;s call this new interace
our debugging interface, and add new constants:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define CDC_IN_DBG_EP  0x83
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define CDC_OUT_DBG_EP 0x04
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define CDC_INT_DBG_EP 0x86
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But a big, important question remains. What are those values, where did they
come from, and why are they defined like that? When originally trying to get
this to work, I put a &lt;strong&gt;TODO&lt;/strong&gt; next to this to figure it out, and had chosen
different constants than what is above. After an hour or so of wondering why
things weren&amp;rsquo;t working quite right, I returned to my gold mine of information
(you read that, right?), to see this explanation of endpoint addresses:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Endpoint Address:

Bits 0..3b Endpoint number
Bits 4..6b Reserved. Set to zero.
Bits 7. Direction 0 = Out, 1 = In (ignored for control endpoints).
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wow, ok. So this means we really can only have 16 endpoints? Even now this seems
a little strange. Moreso because after some trial and (mostly) error, I found
I cannot seem to have, for example, endpoint 0x02 (endpoint 2, direction out)
and endpoint 0x82 (endpoint 2, direction in). So each in/out pair eats 2 of my
16 endpoints. This seems to conflict with what the multi-device demo in CherryUSB
is setting up, but that file is not a complete working example, so I have more
confidence in my own conclusion. My range is simply 0x0-0xF, everything must
be unique, and the top nibble is 0x0 for Host-&amp;gt;Device and 0x8 for Device-&amp;gt;Host.&lt;/p&gt;
&lt;p&gt;So&amp;hellip;what&amp;rsquo;s next? Now we need to get these endpoints in our device descriptor.
There is a line:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;CDC_ACM_DESCRIPTOR_INIT&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x02&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can clearly copy this line, but that&amp;rsquo;s not the whole story. Everything in
the descriptor has lengths, and we&amp;rsquo;re adding our interface descriptors (one
association and 2 interfaces as defined in this macro) to something. Going back
to our gold mine of information, we can see all these interfaces are part of the
&amp;ldquo;configuration&amp;rdquo;, which is basically a defined set of interfaces based on how
you configure the device. We only have one configuration based on high power,
but that configuration needs to be told we&amp;rsquo;ve got two things rather than one.&lt;/p&gt;
&lt;p&gt;Looking at the configuration descriptor, we can see this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;USB_CONFIG_DESCRIPTOR_INIT&lt;/span&gt;(USB_CONFIG_SIZE, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x02&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x01&lt;/span&gt;, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And if we look at the &lt;a href=&#34;https://github.com/sakumisu/CherryUSB/blob/master/common/usb_def.h#L653&#34;&gt;macro definition&lt;/a&gt;, we see it is defined as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define USB_CONFIG_DESCRIPTOR_INIT(wTotalLength, bNumInterfaces, bConfigurationValue, bmAttributes, bMaxPower
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re adding two more interfaces (the association is just an association, it is
not it&amp;rsquo;s own interface&amp;hellip;you can tell because our starting value is 0x02. So,
we need to change our config descriptor init line to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;USB_CONFIG_DESCRIPTOR_INIT&lt;/span&gt;(USB_CONFIG_SIZE, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x04&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x01&lt;/span&gt;, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER),
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And add a new &lt;code&gt;CDC_ACM_DESCRIPTOR_INIT&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;CDC_ACM_DESCRIPTOR_INIT&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0x02&lt;/span&gt;, CDC_INT_DBG_EP, CDC_OUT_DBG_EP, CDC_IN_DBG_EP, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x02&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, we&amp;rsquo;re not done! We need to also change our &lt;code&gt;USB_CONFIG_SIZE&lt;/code&gt; variable,
which states:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN * 1)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We now have 2 &lt;code&gt;CDC_ACM_DESCRIPTOR&lt;/code&gt; elements, so we need to change that to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN * 2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That first parameter is the starting interface number. O and 1 were taken by
our first device, so we&amp;rsquo;ll start at 2. The last number is the string identifier
for the interface. I had tried to change it, but I don&amp;rsquo;t see where that value
is surfaced anywhere in Linux, so I left it as is.&lt;/p&gt;
&lt;p&gt;ok&amp;hellip;now the structure is complete. Just below this are these buffer definitions,
and we&amp;rsquo;ll want to add a debug_buffer:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; debug_buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;2048&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So now I feel it&amp;rsquo;s time to check off a few items from the todo list. This
&lt;code&gt;USB_NOCACHE_RAM_SECTION&lt;/code&gt; must be in CherryUSB, so let&amp;rsquo;s go search for it. It
turns out this is device specific, but for most devices, this boils down to
the addition of a compiler attribute to say the variable is non-cacheable. Here
is an example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define USB_NOCACHE_RAM_SECTION __attribute__((section(&amp;#34;.noncacheable&amp;#34;)))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ok..this is totally fair. We&amp;rsquo;re using these buffers between hardware devices,
so we do not want to cache this. The USB_MEM_ALIGNX is similar, but the attribute
attached will specify the correct alignment for DMA (direct memory access), which
also seems very relevant here.&lt;/p&gt;
&lt;p&gt;Moving down the file, we&amp;rsquo;ll want a new endpoint busy flag, and we&amp;rsquo;ll want at
this point to address what all this &lt;code&gt;dtr_enable&lt;/code&gt; and &lt;code&gt;ep_tx_busy_flag&lt;/code&gt; stuff is
doing. But we can do this in a bit after we make sure Linux is registering two
devices in the first place, so let&amp;rsquo;s move on.&lt;/p&gt;
&lt;p&gt;We see these functions, which we can now tall are all callbacks from USB interrupts
that arrive in CherryUSB, which are then passed to us. So any global variables
here should be marked volatile. We&amp;rsquo;ll want to visit all of these in a bit.&lt;/p&gt;
&lt;p&gt;Next up, endpoint structures. Well, those seem interesting. Let&amp;rsquo;s make sure
our control key, c key, and v key are well dusted off, and practice some copy/paste,
to result in this!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;/*!&amp;lt; endpoint call back */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; usbd_endpoint cdc_out_ep &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ep_addr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CDC_OUT_EP,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ep_cb &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; usbd_cdc_acm_bulk_out
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; usbd_endpoint cdc_in_ep &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ep_addr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CDC_IN_EP,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ep_cb &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; usbd_cdc_acm_bulk_in
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; usbd_interface intf0;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; usbd_interface intf1;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; usbd_endpoint cdc_out_dbg_ep &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ep_addr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CDC_OUT_DBG_EP,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ep_cb &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; usbd_cdc_acm_bulk_out
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; usbd_endpoint cdc_in_dbg_ep &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ep_addr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; CDC_IN_DBG_EP,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ep_cb &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; usbd_cdc_acm_bulk_in
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; usbd_interface intf2;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; usbd_interface intf3;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Well, that was pretty stupid&amp;hellip;very brute force, but it will work. What else
do we need? Well, it looks like a bit more copy/paste work in &lt;code&gt;cdc_acm_init&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_init&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_desc_register&lt;/span&gt;(cdc_descriptor);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* Add primary comms channel */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_add_interface&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_cdc_acm_init_intf&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;intf0));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_add_interface&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_cdc_acm_init_intf&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;intf1));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_add_endpoint&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cdc_out_ep);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_add_endpoint&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cdc_in_ep);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* Add debug log comms channel */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_add_interface&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_cdc_acm_init_intf&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;intf2));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_add_interface&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_cdc_acm_init_intf&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;intf3));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_add_endpoint&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cdc_out_dbg_ep);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_add_endpoint&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cdc_in_dbg_ep);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_initialize&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nothing else seems relevant to the task of getting two interfaces (we don&amp;rsquo;t
need them to actually &lt;strong&gt;WORK&lt;/strong&gt; yet. So let&amp;rsquo;s build/flash/re-plug, and take a
look at &lt;code&gt;sudo dmesg&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[10078355.057650] usb 1-10.3: USB disconnect, device number 56
[10078356.841514] usb 1-10.3: new high-speed USB device number 57 using xhci_hcd
[10078356.966023] usb 1-10.3: New USB device found, idVendor=10b0, idProduct=dead, bcdDevice= 1.00
[10078356.966025] usb 1-10.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[10078356.966026] usb 1-10.3: Product: BL616 Bare Metal
[10078356.966027] usb 1-10.3: Manufacturer: Emil Lerch
[10078356.966027] usb 1-10.3: SerialNumber: 2023-04-19
[10078356.971473] cdc_acm 1-10.3:1.0: ttyACM0: USB ACM device
[10078356.972087] cdc_acm 1-10.3:1.2: ttyACM1: USB ACM device
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The last two lines here indicate we&amp;rsquo;ve got two devices! Let&amp;rsquo;s get them actually
working.&lt;/p&gt;
&lt;h2 id=&#34;making-both-devices-functional&#34;&gt;Making both devices functional&lt;/h2&gt;
&lt;p&gt;So, now we have two devices and 4 endpoints. We&amp;rsquo;ll focus entirely on the &amp;ldquo;IN&amp;rdquo;
endpoints (device to host) that allow us to write hello world. We can debug via
blinky lights, but that is painful, so let&amp;rsquo;s do everything through USB. I mentioned
before I had an hour or two trying to work out the endpoint numbering scheme.
This was a bit of an adventure in debugging that I think is worthwhile to
discuss. But first things first.&lt;/p&gt;
&lt;p&gt;We want to output now to 2 different Linux devices, so we&amp;rsquo;ll want to modify
&lt;code&gt;main.c&lt;/code&gt;. This part is easy, just insert a &lt;code&gt;log()&lt;/code&gt; function in &lt;code&gt;main.c&lt;/code&gt; from
within our while loop, then implement that function in the template file:&lt;/p&gt;
&lt;p&gt;main.c:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (inx&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2000&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_data_send_with_dtr&lt;/span&gt;(write_buffer_main, data_len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;dtr_enabled_true_callbacks:  . Write&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#75715e&#34;&gt;/* cdc_acm_log_with_dtr(write_buffer_main, data_len); */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          inx &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_mtimer_delay_ms&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;cdc_acm_template.c:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* memcpy(&amp;amp;write_buffer[0], data, strlen(data)); */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* write_buffer[9] = 0x30 + debug_val_1; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* write_buffer[20] = 0x30 + debug_val_2; */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;snprintf&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;write_buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ae81ff&#34;&gt;2048&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%d&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;debug u8 val 1: %d, debug val u8 2: %d&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;debug 32 val 1: %d, debug 32 val 2: %d&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;sending to debug...&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        out_inx&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        debug_val_1,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        debug_val_2,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        debug_val32_1,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        debug_val32_2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_data_send_with_dtr&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;write_buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; dbg_len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;snprintf&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        (&lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;)&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;debug_buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ae81ff&#34;&gt;2048&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%d&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;debug u8 val 1: %d, debug val u8 2: %d&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;debug 32 val 1: %d, debug 32 val 2: %d&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;(debug log)&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        out_inx,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        debug_val_1,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        debug_val_2,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        debug_val32_1,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        debug_val32_2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_log_with_dtr&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;debug_buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], dbg_len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;WHOA!&lt;/strong&gt; What is all this? Well, this is my experimentation and debugging in
raw form. Rather than just show you the golden path to make you think, &amp;ldquo;wow, he
has it all sorted out&amp;rdquo;, I wanted to share both failures and successes so you
can better understand how to progressively work through the problem.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with &lt;code&gt;main.c&lt;/code&gt;. I call log with a string, then proceed to ignore
it when we get to the template.c file. At first, I had a lot of problems getting
any output at all. This was due to the values I chose for the endpoint numbers,
and what was going on in the machine was a &amp;ldquo;crossing of wires&amp;rdquo; that was primarily
effecting the &lt;code&gt;dtr_enable&lt;/code&gt; and &lt;code&gt;ep_tx_busy_flag&lt;/code&gt;. In this state, I was able to
get output on &lt;code&gt;/dev/ttyACM0&lt;/code&gt;, but not on &lt;code&gt;/dev/ttyACM1&lt;/code&gt;. I needed to know the
status of these flags at various times, as well as the endpoint numbers coming
through our callback functions &lt;code&gt;usbd_cdc_acm_bulk_out&lt;/code&gt; and &lt;code&gt;usbd_cdc_acm_bulk_in&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Not confident I had a standard library to work with at all, I left space in the
string I used with &lt;code&gt;log&lt;/code&gt;, then poked in my &lt;code&gt;debug_val_1&lt;/code&gt; and &lt;code&gt;debug_val_2&lt;/code&gt; variables
as you can see in the commented comment above in &lt;code&gt;cdc_acm_template.c&lt;/code&gt;. From other
code I could tell &lt;code&gt;memcpy&lt;/code&gt; was available to me, and I took a flyer on &lt;code&gt;strlen&lt;/code&gt;
(yes, I know there are buffer overflow things that can kill me here, but I&amp;rsquo;m
debugging&amp;hellip;leave me be).&lt;/p&gt;
&lt;p&gt;After getting that working, I could see the problem, and I left myself
&lt;a href=&#34;https://github.com/elerch/bl616-usb-cdc-acm/blob/797141a2cdc450c3834476124f483fc6c1741859/cdc_acm_template.c#L197&#34;&gt;some&lt;/a&gt;
&lt;a href=&#34;https://github.com/elerch/bl616-usb-cdc-acm/blob/797141a2cdc450c3834476124f483fc6c1741859/cdc_acm_template.c#L207&#34;&gt;notes&lt;/a&gt;.
Then the fact that memcpy seemed to be builtin rather than requiring some import
was bugging me. I realized that we&amp;rsquo;re not really getting away from this compiler,
which is basically gcc, so what other functions can I just use? A search brought me
to &lt;a href=&#34;https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html&#34;&gt;this page on gcc built-in functions&lt;/a&gt;,
and lo and behold, snprintf was available. With that function, I just needed
to check and make sure that my cast wasn&amp;rsquo;t going to break anything, but it
worked great. With my new found debugging powers, I was able to track down
the endpoint numbering rules above, and finally determine the &lt;code&gt;dtr_enable&lt;/code&gt;
flag, knocking another item off our to do list. DTR is data terminal ready, which
was a modem term and it was unclear whether and how that applied in the land
of USB. Well, it turns out it works great. When the host connects (via &lt;code&gt;screen&lt;/code&gt;
or &lt;code&gt;cat&lt;/code&gt;), Linux notifies the device that the data terminal is ready. The device,
in turn, sends an interrupt, captured by CherryUSB &lt;a href=&#34;https://github.com/sakumisu/CherryUSB/blob/d7c0add7ef58cfa711cf152c088a7e1c65fa5886/class/cdc/usbd_cdc.c#L52-L60&#34;&gt;and sent to the usbd_cdc_acm_set_dtr
function&lt;/a&gt;.
Rather than register this function like the other two, the CherryUSB library
defines an &lt;a href=&#34;https://github.com/sakumisu/CherryUSB/blob/d7c0add7ef58cfa711cf152c088a7e1c65fa5886/class/cdc/usbd_cdc.c#L105&#34;&gt;empty stub function with a __WEAK attribute so a linked object
with another implementation will take priority&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, as long as the endpoints are set up properly, the function will be called
when Linux opens the device, and logs will flow. However, we have two interfaces,
so now we need two variables. An array would probably have been better, but
I just used two (volatile) values:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_cdc_acm_set_dtr&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; intf, &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; dtr)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;/* Based on above init, intf = 0 is normal, intf = 2 is debug */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (dtr) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (intf &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          dtr_enable &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          dtr_debug_enable &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (intf &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          dtr_enable &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          dtr_debug_enable &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;ep_tx_busy_flag&lt;/code&gt; was duplicated in much the same way. This is set to &lt;code&gt;true&lt;/code&gt;
when something is written to the interface, but the library being used is
asynchronous, with the process ending in &lt;code&gt;usbd_cdc_acm_bulk_in&lt;/code&gt;. I don&amp;rsquo;t fully
understand what&amp;rsquo;s going on there, but I don&amp;rsquo;t believe I need to at the moment
either.&lt;/p&gt;
&lt;p&gt;Time for some code cleanup. The work in progress can be viewed in all its glory
&lt;a href=&#34;https://github.com/elerch/bl616-usb-cdc-acm/tree/797141a2cdc450c3834476124f483fc6c1741859&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Simplifying our tool chain: Wrap up</title>
      <link>https://emil.lerch.org/simplifying-our-tool-chain-wrap-up/</link>
      <pubDate>Wed, 26 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://emil.lerch.org/simplifying-our-tool-chain-wrap-up/</guid>
      <description>&lt;h1 id=&#34;simplifying-our-tool-chain-wrap-up&#34;&gt;Simplifying our tool chain: Wrap up&lt;/h1&gt;
&lt;p&gt;This is part of an ongoing series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;./exploring-embedded-programming-with-sipeed-m0s-bl616/&#34;&gt;Part 1: Exploring embedded programming with the Sipeed M0S with the BL616 microprocessor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./getting-to-hello-world-with-sipeed-m0s-bl616/&#34;&gt;Part 2: Getting to hello world with Sipeed m0s (BL616)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./simplifying-our-tool-chain-first-steps/&#34;&gt;Part 3: Simplifying the tool chain: First steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 4: Simplifying the tool chain: Wrap up&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./learning-the-sdk-and-usb-protocol//&#34;&gt;Part 5: Learning the SDK and USB protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./wrapping-up-our-exploration-a-mini-shell/&#34;&gt;Part 6: Wrapping up our exploration: A mini shell&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;ldquo;The next step should be comparatively easy. Swap out the remaining usage of
binary blobs in the toolchain.&amp;rdquo; - me, last week&lt;/p&gt;
&lt;p&gt;Well, that was &lt;strong&gt;NOT&lt;/strong&gt; comparatively easy, not by a long shot. It was difficult
enough that I had to ask myself, &amp;ldquo;so, what exactly is the goal here?&amp;rdquo;. To that
question, I came up with the following answer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reduce disk space usage for all the bits needed to build/flash&lt;/li&gt;
&lt;li&gt;Ensure all build/flash tooling is based on open source&lt;/li&gt;
&lt;li&gt;Get a way to easily use the SDK without a bunch of downloads and setup on the developer machine&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of these three objectives, I can confidently say I&amp;rsquo;ve fully accomplished one of
them, and partially accomplished another.&lt;/p&gt;
&lt;p&gt;Last time, we managed an open source version of the flash tool. With this in
hand, I assumed that we could use a different C compiler (different version
of gcc, or maybe even clang) to compile. The logic was, if I could compile
with clang, I could compile with zig, and I could write embedded code on the
BL616 with zig, which would be way cool. I still have a plan to get there, but
it clearly will not be easy.&lt;/p&gt;
&lt;p&gt;What I found in the process is that this is likely possible, but probably not
desirable. The version of gcc recommended by the Bouffalo SDK is the &lt;a href=&#34;https://github.com/T-head-Semi/xuantie-gnu-toolchain&#34;&gt;Xuantie
T-Head Gnu Toolchain&lt;/a&gt;.
This is a fork of another &lt;a href=&#34;https://github.com/riscv-collab/riscv-gnu-toolchain&#34;&gt;Risc-V collaboration project&lt;/a&gt;
to enable Risc-V compilation.&lt;/p&gt;
&lt;p&gt;Gcc itself, as well as clang, support Risc-V, so compilation is not a problem.
However, the T-Head version supports the RISC-V p extension opcodes enabled
on the BL616. Also, it supports specific tuning for the processor with a
proprietary mtune option.&lt;/p&gt;
&lt;p&gt;Undeterred, I plowed ahead, only to be met with changes that were made to system
includes in the forked toolchain. I was starting to get into some really hairy
stuff that I did not want to maintain. I needed a new approach.&lt;/p&gt;
&lt;p&gt;That new approach would be &amp;ldquo;make sure that there are no binary blobs in the
T-Head toolchain, compile it from scratch, and shove it in a docker container&amp;rdquo;.
As it turns out, this was very possible and the results are in &lt;a href=&#34;https://github.com/elerch/xuantie-gnu-toolchain-docker&#34;&gt;my github
repo here&lt;/a&gt;. Compiling
the code took over an hour, but at the end of the process I got a good base
image.&lt;/p&gt;
&lt;p&gt;The next step was to utilize this base image and load, modify, and configure
the SDK, decompiled post processing tool, and decompiled flash tool from the
previous work. At the end of the day, we have a &lt;a href=&#34;https://github.com/elerch/bouffalo_open_sdk&#34;&gt;docker image&lt;/a&gt;
that can be used in place of make, built with nearly all open source.&lt;/p&gt;
&lt;p&gt;Using this image will consume slightly less disk space (1.8GB vs 2GB), but
we now have an easy way to use the SDK without a bunch of downloads. Unfortunately,
we&amp;rsquo;re not purely open source. The WiFi and BLE drivers remain proprietary,
but everything else is now open.&lt;/p&gt;
&lt;h2 id=&#34;using-the-image-to-create-a-project&#34;&gt;Using the image to create a project&lt;/h2&gt;
&lt;p&gt;The downside to containerizing the SDK is that we now, in our project, need
to reference what I&amp;rsquo;ll call &amp;ldquo;magical things&amp;rdquo;. Generally I don&amp;rsquo;t like magic,
but in this case, I&amp;rsquo;ll accept it. But in the process of working through all
the kinks in the container images, I&amp;rsquo;ve gained an understanding of the
auxiliary files in the project. Here&amp;rsquo;s a tour:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Makefile: make will call cmake to make Makefiles, which end up in a &lt;code&gt;build/&lt;/code&gt;
directory. This Makefile is pure boilerplate and can be used from
project to project with no changes. But the file must be there
so the initial make command kicks off the chain.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CMakeLists.txt: This is a lot of boilerplate, but does specify the source
files, include and main file for the project. It also
specifies the project name. Generally will not be touched
beyond an initial commit, except maybe to add source files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;proj.conf: This is included by CMakeLists, and is intended to set variables
indicating which additional libraries we want to bring in. It is
a bit dubious that something like CherryUSB, which is a different
library and repository, is just included in this SDK, but I don&amp;rsquo;t
want to re-think that right now.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;flash_prog_cfg.ini: This is used by the flash program, specifying the entry
address and a few other variables. It&amp;rsquo;s almost all
boilerplate, but you the filedir variable will need to
change from project to project.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once these files and your source code is in place, we&amp;rsquo;re ready to build. As
long as docker is installed, we can use the following command to build the
project:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker run --rm -t -v $(pwd):/build git.lerch.org/lobo/bouffalo_open_sdk:2f6477f BOARD=bl616dk CHIP=bl616
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once that is complete, we can use this command to flash to the device:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker run --rm --device /dev/ttyACM0 -v $(pwd):/build git.lerch.org/lobo/bouffalo_open_sdk:2f6477f flash BOARD=bl616dk CHIP=bl616 COMX=/dev/ttyACM0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;One permissions note: if using docker proper (rather than podman), build output
will be owned by root. Using &lt;code&gt;-u $(id -u):$(id -g)&lt;/code&gt; as part of the docker commands
above will address that.&lt;/p&gt;
&lt;p&gt;With this done, let&amp;rsquo;s get back to the actual code. The code from part 1, slimmed
down by this exercise, can be found on GitHub, and because we&amp;rsquo;ll use this code
moving forward, here is the link to the current commit: &lt;a href=&#34;https://github.com/elerch/bl616-usb-cdc-acm/tree/797141a2cdc450c3834476124f483fc6c1741859&#34;&gt;https://github.com/elerch/bl616-usb-cdc-acm/tree/797141a2cdc450c3834476124f483fc6c1741859&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Simplifying our tool chain: First steps</title>
      <link>https://emil.lerch.org/simplifying-our-tool-chain-first-steps/</link>
      <pubDate>Sat, 22 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://emil.lerch.org/simplifying-our-tool-chain-first-steps/</guid>
      <description>&lt;h1 id=&#34;simplifying-our-tool-chain-first-steps&#34;&gt;Simplifying our tool chain: First steps&lt;/h1&gt;
&lt;p&gt;This is part of an ongoing series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;./exploring-embedded-programming-with-sipeed-m0s-bl616/&#34;&gt;Part 1: Exploring embedded programming with the Sipeed M0S with the BL616 microprocessor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./getting-to-hello-world-with-sipeed-m0s-bl616/&#34;&gt;Part 2: Getting to hello world with Sipeed m0s (BL616)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 3: Simplifying the tool chain: First steps&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./simplifying-our-tool-chain-wrap-up/&#34;&gt;Part 4: Simplifying the tool chain: Wrap up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./learning-the-sdk-and-usb-protocol//&#34;&gt;Part 5: Learning the SDK and USB protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./wrapping-up-our-exploration-a-mini-shell/&#34;&gt;Part 6: Wrapping up our exploration: A mini shell&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before going any further, the fact that I now have a bunch of random binaries
downloaded from the Internet on my machine, and 2GB of downloaded SDKs, I really
want to deal with that issue.&lt;/p&gt;
&lt;p&gt;First on the list&amp;hellip;the Sipeed SDK can be removed. We&amp;rsquo;re getting hello world
through the Bouffalo SDK, and while there are differences that we haven&amp;rsquo;t completely
figured out, let&amp;rsquo;s remove 1GB of stuff from my machine until it&amp;rsquo;s needed.&lt;/p&gt;
&lt;p&gt;Secondly, the flash software. This is &lt;a href=&#34;https://github.com/bouffalolab/bouffalo_sdk/issues/93&#34;&gt;closed source&lt;/a&gt;
(but distributed in an Apache 2.0 licensed repo), but if we run &lt;code&gt;strings BLFlashCommand-ubuntu&lt;/code&gt;
on it, we&amp;rsquo;ll see that it&amp;rsquo;s clearly a &lt;a href=&#34;https://pyinstaller.org&#34;&gt;pyinstaller&lt;/a&gt; executable. Anyone else
more than a little concerned it is closed source for &amp;ldquo;security&amp;rdquo;? Anyway, time for
some reverse engineering. We&amp;rsquo;ll get to (a similar implementation of) the
the flash loader in Python, giving us a) source code we can examine b) smaller
disk footprint, and c) access to more platforms. &lt;a href=&#34;https://lupyuen.github.io/articles/loader&#34;&gt;The MCU side of this was done&lt;/a&gt;
using &lt;a href=&#34;https://ghidra-sre.org/&#34;&gt;Ghidra&lt;/a&gt;, which was cool, but I think we can
get better fidelity this way.&lt;/p&gt;
&lt;h2 id=&#34;reversing-blflashcommand&#34;&gt;Reversing BLFlashCommand&lt;/h2&gt;
&lt;p&gt;Easy part. Go to &lt;a href=&#34;https://pyinstxtractor-web.netlify.app/&#34;&gt;https://pyinstxtractor-web.netlify.app/&lt;/a&gt;, and process the
executable. We&amp;rsquo;ll get a zip file of the original pyc and system libraries.
Now we can ditch the system libraries because we&amp;rsquo;ll just use the system
interpreter and libraries.&lt;/p&gt;
&lt;p&gt;Python decompilers are kind of fickle, so let&amp;rsquo;s figure out the &lt;em&gt;EXACT&lt;/em&gt; version
of Python that created these. It turns out, the first two bytes indicate that
exact version. We&amp;rsquo;ll run &lt;code&gt;od -x BLFlashCommand.pyc |head&lt;/code&gt; and get the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;0000000 0d42 0a0d 0000 0000 0000 0000 0000 0000
0000020 00e3 0000 0000 0000 0000 0000 0500 0000
0000040 4000 0000 7300 00b8 0000 0064 005a 0164
0000060 0264 016c 015a 0164 0264 026c 025a 0164
0000100 0264 036c 035a 0164 0264 046c 045a 0164
0000120 0264 056c 055a 0164 0264 066c 065a 0164
0000140 0264 076c 075a 0164 0364 086c 096d 095a
0000160 0001 0164 0464 0a6c 0b6d 0b5a 0001 0164
0000200 0564 0c6c 0d6d 0d5a 0001 0664 0764 0084
0000220 0e5a 0047 0864 0964 0084 0964 0665 0f6a
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That &lt;code&gt;0d42&lt;/code&gt; converts to decimal 3394, which we can look up at &lt;a href=&#34;https://github.com/python/cpython/blob/main/Lib/importlib/_bootstrap_external.py#L339&#34;&gt;https://github.com/python/cpython/blob/main/Lib/importlib/_bootstrap_external.py#L339&lt;/a&gt;
to find it was Python 3.7b5. This was the last of the Python 3.7 bytecode versions,
so we know it was a production version of 3.7, and beyond that, it shouldn&amp;rsquo;t matter.&lt;/p&gt;
&lt;p&gt;Decompilers are an inexact science, so let&amp;rsquo;s get to work. First, we need to
distill to exactly what we need. We dumped all the dynamic libraries (on linux,
everything *.so can go. The high level action plan is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Remove all the standard library stuff, so we are at least focusing on additional code&lt;/li&gt;
&lt;li&gt;Decompile the rest with &lt;a href=&#34;https://pypi.org/project/uncompyle6/&#34;&gt;Uncompyle6&lt;/a&gt;. Ignore all failures&lt;/li&gt;
&lt;li&gt;Create a brand new directory&lt;/li&gt;
&lt;li&gt;Copy BLFlashCommand.py into the new directory&lt;/li&gt;
&lt;li&gt;Establish a Python virtual environment in the new directory and activate it&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;python3 BLFlashCommand.py&lt;/code&gt; and catch the error&lt;/li&gt;
&lt;li&gt;Fix the error, go to step 6 until no errors&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is easier than it sounds, and ultimately I switched to &lt;a href=&#34;https://pypi.org/project/decompyle3/&#34;&gt;decompyle3&lt;/a&gt;
and &lt;a href=&#34;https://github.com/elerch/python37-decompilers&#34;&gt;created a docker image to help&lt;/a&gt;.
The errors, generally, will fall into one of the following categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Missing import: copy the py file from the corresponding decompiled directory&lt;/li&gt;
&lt;li&gt;Missing import, that import is a library in &lt;a href=&#34;https://pypi.org&#34;&gt;PyPi&lt;/a&gt;: add a reference to
requirements.txt, run &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;. Note that figuring
out the right library is a bit tricky, but helping us is the version numbers
in the decompiled source code&lt;/li&gt;
&lt;li&gt;Import conflict: This happens when, for instance &lt;code&gt;base.py&lt;/code&gt; and a directory &lt;code&gt;base&lt;/code&gt;
both existed and were copied over. We need to delete &lt;code&gt;base.py&lt;/code&gt;. Python&amp;rsquo;s error
messages were a bit confusing when this occurred in nested directories&lt;/li&gt;
&lt;li&gt;Syntax error: Decompilation error. Ultimately, I found several decompilers
and had to pick one that worked.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eventually it ran as far as providing usage information, so I ran it with the
same arguments as I saw out of &lt;code&gt;make flash&lt;/code&gt;. This gave me a new error related
to config files, which I located in the SDK directory and pulled in.&lt;/p&gt;
&lt;p&gt;Next, we have a partially working flash command that successfully spins eating
memory. Time to bust out some normal debugging skills and get to work. And now
that I&amp;rsquo;ve found 3 decompilers, I can compare their output to locate potential
sources of errors. To help me with this, I built a &lt;a href=&#34;https://github.com/elerch/python37-decompilers&#34;&gt;docker image with all three
compilers&lt;/a&gt;. I just ran this on
all relevant &lt;code&gt;pyc&lt;/code&gt; files so I&amp;rsquo;d have versions to compare.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll skip the gory details here, but a few hours later, I have a fully working,
&amp;ldquo;open source&amp;rdquo; version of the BLFlashCommand flash command, located here:
&lt;a href=&#34;https://git.lerch.org/lobo/blflashcommand&#34;&gt;https://git.lerch.org/lobo/blflashcommand&lt;/a&gt;. The spinning and eating memory, for
the curious, was a problem in the configuration parser. I ultimately threw away
all the code there and replaced it with Python standard library stuff.&lt;/p&gt;
&lt;p&gt;You can see the changes I&amp;rsquo;ve made to the decompilation output (including one
file that was nearly completely removed and replaced with a small shim) in the
change logs on that repo.&lt;/p&gt;
&lt;p&gt;The next step should be comparatively easy. Swap out the remaining usage of
binary blobs in the toolchain.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Getting to hello world with Sipeed m0s (BL616)</title>
      <link>https://emil.lerch.org/getting-to-hello-world-with-sipeed-m0s-bl616/</link>
      <pubDate>Sun, 16 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://emil.lerch.org/getting-to-hello-world-with-sipeed-m0s-bl616/</guid>
      <description>&lt;h1 id=&#34;getting-to-hello-world-with-sipeed-m0s-bl616&#34;&gt;Getting to hello world with Sipeed m0s (BL616)&lt;/h1&gt;
&lt;p&gt;This post is part of a series.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;./exploring-embedded-programming-with-sipeed-m0s-bl616/&#34;&gt;Part 1: Exploring embedded programming with the Sipeed M0S with the BL616 microprocessor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Part 2: Getting to hello world with Sipeed m0s (BL616)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./simplifying-our-tool-chain-first-steps/&#34;&gt;Part 3: Simplifying our tool chain: First steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./simplifying-our-tool-chain-wrap-up/&#34;&gt;Part 4: Simplifying the tool chain: Wrap up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./learning-the-sdk-and-usb-protocol//&#34;&gt;Part 5: Learning the SDK and USB protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./wrapping-up-our-exploration-a-mini-shell/&#34;&gt;Part 6: Wrapping up our exploration: A mini shell&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&amp;rsquo;ve been following along, we have 3 to do&amp;rsquo;s left after getting to blinky
with this device. These are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What’s the difference between Bouffalo Labs’ SDK and Sipeed’s repo?&lt;/li&gt;
&lt;li&gt;Avoid binary toolchains&lt;/li&gt;
&lt;li&gt;Figure out what’s up with hello world&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So far, we&amp;rsquo;ve ignored #1 and #2, and bypassed #3 on the way to get something
happening on the device. Along the way, we&amp;rsquo;ve learned how to build code for the
device, we&amp;rsquo;ve learned how to flash it, and where the serial console lives. This
has generated a few more questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Can I just stick wires into these holes in the board?&lt;/li&gt;
&lt;li&gt;Can I move to other GPIO pins?&lt;/li&gt;
&lt;li&gt;Can I route communications directly through the USB port I’m already plugged into?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My style is generally, &amp;ldquo;let&amp;rsquo;s follow the easy path the vendor has laid out, then
understand it deeply until I can go my own way&amp;rdquo;. But I like to stay as easy as
possible. Easy here does &lt;strong&gt;NOT&lt;/strong&gt; mean busting out a USB to TTL device. I have
evidence in front of me to suggest that&amp;rsquo;s ultimately unnecessary. First, Sipeed
did not add pins to the dock to support this&amp;hellip;they made that hard. This
suggests that it&amp;rsquo;s not &amp;ldquo;the way&amp;rdquo; they really wanted us to use that dock. Secondly,
when I first plugged this device into my computer, I got both power and data,
in the form of &lt;code&gt;/dev/ttyACM0&lt;/code&gt;, and indeed that&amp;rsquo;s still the way we flash things
on to the device. With that in mind, let&amp;rsquo;s tackle the question of &amp;ldquo;Can I route
communications directly through the USB port I&amp;rsquo;m already plugged into&amp;rdquo;. My senses
point me to the idea that this is the right direction.&lt;/p&gt;
&lt;p&gt;So, first step, let&amp;rsquo;s consider that all this is very &amp;ldquo;M0S Dev Dock&amp;rdquo; specific.
This suggests that I&amp;rsquo;m barking up the wrong tree, and rather than Bouffalo Labs&#39;
SDK, I should start looking at the Sipeed fork. Let&amp;rsquo;s go.&lt;/p&gt;
&lt;h2 id=&#34;whats-the-difference-between-bouffalo-labs-sdk-and-sipeeds-repo&#34;&gt;What&amp;rsquo;s the difference between Bouffalo Labs&amp;rsquo; SDK and Sipeed&amp;rsquo;s repo?&lt;/h2&gt;
&lt;p&gt;First, we&amp;rsquo;ll &lt;code&gt;git clone https://github.com/sipeed/M0S_BL616_example&lt;/code&gt;. In GitHub,
this repo isn&amp;rsquo;t listed as a fork, but it is. It appears to have been cloned
locally, then pushed up. And maybe with some cherry picks after the fact. Let&amp;rsquo;s
figure that out.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ git clone https://github.com/sipeed/M0S_BL616_example
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd M0S_BL616_example
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ git remote add upstream https://github.com/bouffalolab/bouffalo_sdk
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ git merge-base main upstream/master
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;fce6ce539e98e17f87aff39989cee8fa222499de
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ git log -1 fce6ce539e98e17f87aff39989cee8fa222499de
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;commit fce6ce539e98e17f87aff39989cee8fa222499de
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Author: jzlv &amp;lt;jzlv@bouffalolab.com&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date:   Wed Dec &lt;span style=&#34;color:#ae81ff&#34;&gt;21&lt;/span&gt; 20:33:14 &lt;span style=&#34;color:#ae81ff&#34;&gt;2022&lt;/span&gt; +0800
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;chore&lt;span style=&#34;color:#f92672&#34;&gt;][&lt;/span&gt;cmake&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; move app target source into board
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;They diverged in December. But, I see other commits pulled into the sipeed version.
For instance, commit &lt;code&gt;f1e8545&lt;/code&gt; in the Bouffalo SDK is the same as &lt;code&gt;fd2377eb&lt;/code&gt;
in the Sipeed version. Clear evidence that these were cherry picked commits, which
will make this job a lot harder.&lt;/p&gt;
&lt;p&gt;Looking more deeply, git reports 78 commits added to M0S and 76 commits added to
Bouffalo. Looking with a diff program, we see 337 differences between the repos
at the time of writing. Ultimately, we may need to put a pin in this one.&lt;/p&gt;
&lt;p&gt;One interesting difference that sticks out, however, is the presence of a
&lt;code&gt;sipeed/solutions&lt;/code&gt; directory that includes a few directories, including a
&amp;ldquo;usbd_cdc_acm_with_uart&amp;rdquo; subdirectory. Well this looks interesting. Let&amp;rsquo;s play
with that. Without looking at the code yet, this looks like a USB Daemon (usbd)
(UPDATE: this is wrong - more below) with Linux Communication Device Class (cdc)
ACM. So intuitively I expect to load this code and see a &lt;code&gt;/dev/ttyACM0&lt;/code&gt; device
pop back up. What it does from there?  Who knows.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll use our docker container and run &lt;code&gt;make CHIP=bl616 BOARD=bl616dk&lt;/code&gt;. Unplug
our device, hold the boot button and plug it back in. Release the boot button.
From inside the same directory, we&amp;rsquo;ll &lt;code&gt;make CHIP=bl616 BOARD=bl616dk&lt;/code&gt;, and see
the following:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;./../../../tools/bflb_tools/bouffalo_flash_cube/BLFlashCommand-ubuntu \
--interface=uart \
--baudrate=2000000 \
--port=/dev/ttyACM0 \
--chipname=bl616 \
--cpu_id= \
--config=flash_prog_cfg.ini
[&amp;#39;./../../../tools/bflb_tools/bouffalo_flash_cube/BLFlashCommand-ubuntu&amp;#39;, &amp;#39;--interface=uart&amp;#39;, &amp;#39;--baudrate=2000000&amp;#39;, &amp;#39;--port=/dev/ttyACM0&amp;#39;, &amp;#39;--chipname=bl616&amp;#39;, &amp;#39;--cpu_id=&amp;#39;, &amp;#39;--config=flash_prog_cfg.ini&amp;#39;]
[21:30:12.808] - Serial port is /dev/ttyACM0
[21:30:12.808] - ==================================================
[21:30:12.811] - Program Start
[21:30:12.811] - ========= eflash loader cmd arguments =========
[21:30:12.812] - serial port is /dev/ttyACM0
[21:30:12.812] - chiptype: bl616
[21:30:12.812] - cpu_reset=False
[21:30:12.814] - ========= Interface is uart =========
[21:30:12.814] - Bootrom load
[21:30:12.814] - ========= get_boot_info =========
[21:30:12.814] - ========= image get bootinfo =========
[21:30:13.068] - tx rx and power off, press the machine!
[21:30:13.068] - cutoff time is 0.05
[21:30:13.118] - power on tx and rx
[21:30:14.022] - reset cnt: 0, reset hold: 0.05, shake hand delay: 0.1
[21:30:14.022] - clean buf
[21:30:14.023] - send sync
[21:30:14.224] - ack is b&amp;#39;4f4b&amp;#39;
[21:30:14.254] - shake hand success
[21:30:14.755] - data read is b&amp;#39;010016060000010027928001319735cf0eb417000f758010&amp;#39;
[21:30:14.755] - ========= ChipID: b40ecf359731 =========
[21:30:14.755] - Get bootinfo time cost(ms): 1940.98583984375
[21:30:14.755] - change bdrate: 2000000
[21:30:14.755] - Clock PLL set
[21:30:14.755] - Set clock time cost(ms): 0.19287109375
[21:30:14.866] - Read mac addr
[21:30:14.867] - flash set para
[21:30:14.867] - get flash pin cfg from bootinfo: 0x02
[21:30:14.867] - set flash cfg: 14102
[21:30:14.867] - Set flash config
[21:30:14.869] - Set para time cost(ms): 1.668701171875
[21:30:14.869] - ========= flash read jedec ID =========
[21:30:14.870] - Read flash jedec ID
[21:30:14.870] - readdata:
[21:30:14.870] - b&amp;#39;c8601600&amp;#39;
[21:30:14.870] - Finished
[21:30:14.876] - Program operation
[21:30:14.876] - Dealing Index 0
[21:30:14.876] - ========= programming /home/lobo/bouffalo_sdk/M0S_BL616_example/sipeed/solutions/usbd_cdc_acm_with_uart/./build/build_out/usbd_cdc_acm_with_uart_bl616.bin to 0x000000
[21:30:14.878] - flash para file: /home/lobo/bouffalo_sdk/M0S_BL616_example/tools/bflb_tools/bouffalo_flash_cube/chips/bl616/efuse_bootheader/flash_para.bin
[21:30:14.878] - Set flash config
[21:30:14.879] - Set para time cost(ms): 1.68408203125
[21:30:14.879] - ========= flash load =========
[21:30:14.879] - ========= flash erase =========
[21:30:14.879] - Erase flash  from 0x0 to 0xc89f
[21:30:15.045] - Erase time cost(ms): 165.34619140625
[21:30:15.049] - Load 2048/51360 {&amp;#34;progress&amp;#34;:3}
[21:30:15.053] - Load 4096/51360 {&amp;#34;progress&amp;#34;:7}
[21:30:15.058] - Load 6144/51360 {&amp;#34;progress&amp;#34;:11}
[21:30:15.062] - Load 8192/51360 {&amp;#34;progress&amp;#34;:15}
[21:30:15.066] - Load 10240/51360 {&amp;#34;progress&amp;#34;:19}
[21:30:15.070] - Load 12288/51360 {&amp;#34;progress&amp;#34;:23}
[21:30:15.074] - Load 14336/51360 {&amp;#34;progress&amp;#34;:27}
[21:30:15.078] - Load 16384/51360 {&amp;#34;progress&amp;#34;:31}
[21:30:15.082] - Load 18432/51360 {&amp;#34;progress&amp;#34;:35}
[21:30:15.086] - Load 20480/51360 {&amp;#34;progress&amp;#34;:39}
[21:30:15.090] - Load 22528/51360 {&amp;#34;progress&amp;#34;:43}
[21:30:15.094] - Load 24576/51360 {&amp;#34;progress&amp;#34;:47}
[21:30:15.098] - Load 26624/51360 {&amp;#34;progress&amp;#34;:51}
[21:30:15.102] - Load 28672/51360 {&amp;#34;progress&amp;#34;:55}
[21:30:15.106] - Load 30720/51360 {&amp;#34;progress&amp;#34;:59}
[21:30:15.111] - Load 32768/51360 {&amp;#34;progress&amp;#34;:63}
[21:30:15.115] - Load 34816/51360 {&amp;#34;progress&amp;#34;:67}
[21:30:15.119] - Load 36864/51360 {&amp;#34;progress&amp;#34;:71}
[21:30:15.123] - Load 38912/51360 {&amp;#34;progress&amp;#34;:75}
[21:30:15.127] - Load 40960/51360 {&amp;#34;progress&amp;#34;:79}
[21:30:15.131] - Load 43008/51360 {&amp;#34;progress&amp;#34;:83}
[21:30:15.135] - Load 45056/51360 {&amp;#34;progress&amp;#34;:87}
[21:30:15.138] - Load 47104/51360 {&amp;#34;progress&amp;#34;:91}
[21:30:15.142] - Load 49152/51360 {&amp;#34;progress&amp;#34;:95}
[21:30:15.146] - Load 51200/51360 {&amp;#34;progress&amp;#34;:99}
[21:30:15.147] - Load 51360/51360 {&amp;#34;progress&amp;#34;:100}
[21:30:15.147] - Load 51360/51360 {&amp;#34;progress&amp;#34;:100}
[21:30:15.147] - Write check
[21:30:15.147] - Flash load time cost(ms): 102.0771484375
[21:30:15.147] - Finished
[21:30:15.147] - Sha caled by host: 8161b63f09e30787bd4ff7c3a28be1aed67fdb18c94a21fc757b2d6d5a5501a5
[21:30:15.147] - xip mode Verify
[21:30:15.190] - Read Sha256/51360
[21:30:15.190] - Flash xip readsha time cost(ms): 42.87548828125
[21:30:15.190] - Finished
[21:30:15.191] - Sha caled by dev: 8161b63f09e30787bd4ff7c3a28be1aed67fdb18c94a21fc757b2d6d5a5501a5
[21:30:15.191] - Verify success
[21:30:15.191] - Program Finished
[21:30:15.191] - All time cost(ms): 2379.883056640625
[21:30:15.292] - close interface
[21:30:15.292] - [All Success]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;ok. Now we can unplug it and plug it in normally (no boot button). Before we
do that, I&amp;rsquo;ll issue &lt;code&gt;sudo dmesg -w&lt;/code&gt; so I can watch the kernel logs. Here&amp;rsquo;s
the result:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[9080153.452919] usb 1-10.3: new high-speed USB device number 55 using xhci_hcd
[9080153.581289] usb 1-10.3: config 1 interface 0 altsetting 0 endpoint 0x83 has an invalid bInterval 0, changing to 7
[9080153.581570] usb 1-10.3: New USB device found, idVendor=349b, idProduct=6160, bcdDevice= 2.00
[9080153.581575] usb 1-10.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[9080153.581579] usb 1-10.3: Product: Bouffalo CDC DEMO
[9080153.581582] usb 1-10.3: Manufacturer: Bouffalo
[9080153.583841] cdc_acm 1-10.3:1.0: ttyACM0: USB ACM device
[9080322.418367] usb 1-10.3: USB disconnect, device number 55
[9080326.010047] usb 1-10.3: new high-speed USB device number 56 using xhci_hcd
[9080326.138678] usb 1-10.3: New USB device found, idVendor=359f, idProduct=0000, bcdDevice= 1.00
[9080326.138684] usb 1-10.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[9080326.138687] usb 1-10.3: Product: USBD CDC ACM WITH UART
[9080326.138690] usb 1-10.3: Manufacturer: SIPEED
[9080326.138692] usb 1-10.3: SerialNumber: 2023030911
[9080326.141672] cdc_acm 1-10.3:1.0: ttyACM0: USB ACM device
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;IT LOOKS GOOD! What we can see here in the logs is a USB device being plugged in.
This was listed as &lt;strong&gt;Product: Bouffalo CDC DEMO&lt;/strong&gt;. This event was triggered when
I plugged the device in while holding the boot button, allowing me to add the firmware.
Next, we see that I&amp;rsquo;ve disconnected the device (USB disconnect, device number 55).
Finally, when I plug the device back in, Linux sees a new USB ACM device, called
&lt;strong&gt;Product: USBD CDC ACM WITH UART&lt;/strong&gt;. Can we do anything with this?&lt;/p&gt;
&lt;p&gt;&lt;code&gt;screen /dev/ttyACM0 2000000&lt;/code&gt; and&amp;hellip;nothing. So - limited success. Maybe we should
actually look at this code to see what it&amp;rsquo;s doing?&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll head into M0S_BL616_example/sipeed/solutions/usbd_cdc_acm_with_uart and
take a look at &lt;code&gt;main.c&lt;/code&gt;. Scanning the code, I see &lt;a href=&#34;https://github.com/sipeed/M0S_BL616_example/blob/main/sipeed/solutions/usbd_cdc_acm_with_uart/main.c#L120-L144&#34;&gt;this function&lt;/a&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;uart1_init&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    uart1 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_device_get_by_name&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;uart1&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    s_cdc_line_coding.dwDTERate &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2000000&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    s_cdc_line_coding.bDataBits &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;8&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    s_cdc_line_coding.bParityType &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    s_cdc_line_coding.bCharFormat &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_init&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        uart1,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; bflb_uart_config_s){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .baudrate &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; (s_cdc_line_coding.dwDTERate),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .data_bits &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UART_DATA_BITS_5 &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (s_cdc_line_coding.bDataBits &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .stop_bits &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UART_STOP_BITS_0_5 &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (s_cdc_line_coding.bCharFormat &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .parity &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UART_PARITY_NONE &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (s_cdc_line_coding.bParityType),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .flow_ctrl &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .tx_fifo_threshold &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .rx_fifo_threshold &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_irq_attach&lt;/span&gt;(uart1&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;irq_num, uart_isr, NULL);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_irq_enable&lt;/span&gt;(uart1&lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt;irq_num);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_rxint_mask&lt;/span&gt;(uart1, false);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This confirms the bit rate is appropriate, so our screen command is good. What
is it doing exactly? Well, apparently that usb port is connected to UART1, so
that&amp;rsquo;s useful information. In order to receive data, it will need an interrupt,
so we see IRQ attach and enable commands. I&amp;rsquo;m not sure what the rxint_mask is
at this point, but it looks like we&amp;rsquo;re turning off any mask. So that isn&amp;rsquo;t in
our way.&lt;/p&gt;
&lt;p&gt;The processor will need to know the code to call when the receive interrupt is
triggered. So I&amp;rsquo;m guessing there is a &amp;ldquo;uart_isr&amp;rdquo; function somewhere in this
code. ISR typically also stands for Interrupt Service Routine. Good signs all
around, so I check, and sure enough, it&amp;rsquo;s the function just above our init function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;uart_isr&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; irq, &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;arg)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; intstatus &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_get_intstatus&lt;/span&gt;(uart1);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (intstatus &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; UART_INTSTS_RX_FIFO) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG_D&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rx fifo: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_rxavailable&lt;/span&gt;(uart1)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_getchar&lt;/span&gt;(uart1);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG_RT&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0x%02x&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, c);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;Ring_Buffer_Write_Byte&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;uart1_rx_rb, c);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG_RD&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_feature_control&lt;/span&gt;(uart1, UART_CMD_SET_RTS_VALUE, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (intstatus &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; UART_INTSTS_RTO) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG_D&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rto: &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_rxavailable&lt;/span&gt;(uart1)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_getchar&lt;/span&gt;(uart1);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG_RT&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%02x &amp;#34;&lt;/span&gt;, c);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;Ring_Buffer_Write_Byte&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;uart1_rx_rb, c);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG_RD&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_int_clear&lt;/span&gt;(uart1, UART_INTCLR_RTO);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (intstatus &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt; UART_INTSTS_TX_FIFO) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG_D&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tx fifo&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_txint_mask&lt;/span&gt;(uart1, true);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Looking at this function, we have a general structure of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the status&lt;/li&gt;
&lt;li&gt;Status can be one of three things: receive fifo, rto or tx fifo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m not entirely sure what these do. Receive fifo means &amp;ldquo;receive first in, first
out&amp;rdquo;, so we see a loop to gather each character and add it to a ring buffer.
After reading all characters, we see this code set the UART to RTS, or &amp;ldquo;ready
to send&amp;rdquo;. That&amp;rsquo;s clearly the primary &amp;ldquo;receive some stuff over the line&amp;rdquo; path.&lt;/p&gt;
&lt;p&gt;RTO&amp;hellip;I need to investigate. A quick search tells me RTO is &amp;ldquo;Receiver Timeout&amp;rdquo;.
Many timeouts in low level programming are fully expected, so we shouldn&amp;rsquo;t assume (yet)
that this timeout is a bad thing. Scanning the SDK &lt;code&gt;grep -ri rto&lt;/code&gt; and weeding
out the FreeRTOS references, it&amp;rsquo;s looking like RTO is configured and it&amp;rsquo;s
probably just a normal part of life here, but we do see an attempt to flush
whatever characters are in the uart at that point and add them to the ring buffer.&lt;/p&gt;
&lt;p&gt;TX_FIFO. This is clearly &amp;ldquo;we&amp;rsquo;re transmitting some stuff&amp;rdquo;. We set a mask here
of some sort, but at this point, I have no idea what or why.&lt;/p&gt;
&lt;p&gt;The other interesting thing here is the multiple &lt;code&gt;LOG_D&lt;/code&gt; functions. This leads
me to strongly suspect logging statements are sent out to JTag, but I haven&amp;rsquo;t
tackled that yet either. My hope here is to not run down that rabbit hole.&lt;/p&gt;
&lt;h2 id=&#34;software-protocols-and-interfaces&#34;&gt;Software, protocols and interfaces&lt;/h2&gt;
&lt;p&gt;We are now much more in software, and the fact that I&amp;rsquo;m looking at ring buffers
makes me a lot more comfortable. Without completing my to do list, I know a way
to get the SDK to create a USB ACM device in Linux, so now it&amp;rsquo;s &lt;em&gt;just&lt;/em&gt; a matter
of code. Let&amp;rsquo;s dig around more and see what we can see.&lt;/p&gt;
&lt;p&gt;First, I&amp;rsquo;ll take a look at the main function from the same &lt;code&gt;main.c&lt;/code&gt; file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;board_init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;uart1_gpio_init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; uartx_rx_buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;512&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;Ring_Buffer_Init&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;uart1_rx_rb, uartx_rx_buffer, &lt;span style=&#34;color:#66d9ef&#34;&gt;sizeof&lt;/span&gt;(uartx_rx_buffer), NULL, NULL);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;uart1_init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;extern&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_cdc_acm_template_init&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_cdc_acm_template_init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG_D&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;start loop&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; loop_count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;extern&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;volatile&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; ep_tx_busy_flag;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (ep_tx_busy_flag)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; uart1_rx_rb_len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ring_Buffer_Get_Length&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;uart1_rx_rb);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;uart1_rx_rb_len)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (uart1_rx_rb_len &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;512&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; loop_count&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        loop_count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; data[uart1_rx_rb_len];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; uart1_rx_rb_len_acc &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Ring_Buffer_Read&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;uart1_rx_rb, data, uart1_rx_rb_len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;uart1_rx_rb_len_acc)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG_D&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;acc: %u, uart1_rx_rb_len: %u&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, uart1_rx_rb_len_acc, uart1_rx_rb_len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;size_t&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; uart1_rx_rb_len_acc; i&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG_RD&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%c&amp;#34;&lt;/span&gt;, data[i]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;LOG_RD&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;csi_dcache_clean_invalid_range&lt;/span&gt;(data, uart1_rx_rb_len_acc);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ep_tx_busy_flag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_ep_start_write&lt;/span&gt;(CDC_IN_EP, data, uart1_rx_rb_len_acc);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;re running truly bare metal, you can&amp;rsquo;t ever return. When you return,
the device has no more code to run, so it simply halts. So along with all the
other &amp;ldquo;non-OS&amp;rdquo; examples, we see here an infinite loop. Usually this is done
with &lt;code&gt;while (1)&lt;/code&gt;, but here they wanted to initialize a variable, so they went
with a &lt;code&gt;for&lt;/code&gt; statement with an empty evaluation expression (denoted by the
empty space between those two colons).&lt;/p&gt;
&lt;p&gt;First thing, we see a &lt;code&gt;volatile&lt;/code&gt; variable. This is common with hardware. If
a compiler sees a variable, it will happily optimize it in tons of ways to make
your code faster and more efficient. However, memory is used a lot between
threads or even by devices (using memory mapped I/O), to communicate. So
the volatile keyword lets the compiler know that the value of this variable
may change without the compiler&amp;rsquo;s ability to predict that change. Searching
for &lt;code&gt;ep_tx_busy_flag&lt;/code&gt;, I see hits in the &lt;code&gt;src/cdc_acm_template.c&lt;/code&gt;
file. My guess at this point is that there is an interrupt triggered when data is
sent, and we use that and a timeout interrupt to turn the flag on and off.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s also at this point where I notice the Bouffalo Labs SDK has an &lt;code&gt;examples/peripherals&lt;/code&gt;
directory with some similar code. However, the Sipeed example I&amp;rsquo;m focused on
at the moment is much more built-out.&lt;/p&gt;
&lt;p&gt;The loop doesn&amp;rsquo;t do anything if a) we&amp;rsquo;re busy, b) we have received no data, or
c) we either haven&amp;rsquo;t received a lot of data or we haven&amp;rsquo;t spent a lot of time
waiting. This last check (&lt;code&gt;if (uart_1_rx_rb_len &amp;lt; 512 &amp;amp;&amp;amp; loop_count++ &amp;lt; 1000)&lt;/code&gt; )
isn&amp;rsquo;t too thrilling. The time for a processor to loop 1000 times will vary from
model to model, and maybe even what it&amp;rsquo;s doing. We&amp;rsquo;re super low level though,
so access to clocks is&amp;hellip;complicated. In any case, we seek to understand at the
moment, rather than go changing anything.&lt;/p&gt;
&lt;p&gt;Assuming all those checks pass, we&amp;rsquo;ll actually read the ring buffer. There&amp;rsquo;s a bit
of a race condition here, so there&amp;rsquo;s one final check to make sure we were actually
able to read something. Then we log the heck out of everything&amp;hellip;to JTag. There&amp;rsquo;s a final
cleaning bit that I&amp;rsquo;m not sure about, then we issue &lt;code&gt;usbd_ep_start_write&lt;/code&gt;, which
I would expect to write the data back out (like an echo), but we didn&amp;rsquo;t see that
happen&amp;hellip;so I&amp;rsquo;m not sure. I&amp;rsquo;m also not sure what this &lt;code&gt;ep&lt;/code&gt; part of all the names
are. Another item for the to do list. But we&amp;rsquo;re close to actually &lt;em&gt;writing&lt;/em&gt;
some code rather than just reading it.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s late though, so I need to come back to this. I do want to actually write
something, so let&amp;rsquo;s start with something simple. I found the manufacturer strings
in &lt;code&gt;src/include&lt;/code&gt;, and successfully changed them. Each character is 2 bytes, plus
another 2 bytes for the the &lt;code&gt;USB_DESCRIPTOR_TYPE_STRING&lt;/code&gt;. This allows me to
calculate the length of the string. Changing the code in this file from:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0x0E&lt;/span&gt;,                       &lt;span style=&#34;color:#75715e&#34;&gt;/* bLength */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    USB_DESCRIPTOR_TYPE_STRING, &lt;span style=&#34;color:#75715e&#34;&gt;/* bDescriptorType */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;S&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar0 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;I&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar1 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;P&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar2 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;E&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar3 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;E&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar4 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;D&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar5 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;0x16&lt;/span&gt;,                       &lt;span style=&#34;color:#75715e&#34;&gt;/* bLength */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    USB_DESCRIPTOR_TYPE_STRING, &lt;span style=&#34;color:#75715e&#34;&gt;/* bDescriptorType */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;E&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar0 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;m&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar1 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;i&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar2 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;l&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar3 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar4 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;L&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar5 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;e&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar6 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;r&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar7 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;c&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar8 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;h&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x00&lt;/span&gt;,                  &lt;span style=&#34;color:#75715e&#34;&gt;/* wcChar9 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Allows me to see the new Manufacturer name &amp;ldquo;Emil Lerch&amp;rdquo; in &lt;code&gt;dmesg&lt;/code&gt; and &lt;code&gt;lsusb&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Bus 001 Device 063: ID 359f:0000 Emil Lerch USBD CDC ACM WITH UART
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this first change we&amp;rsquo;ve demonstrated at least a beginning of understanding.
And a stumbled-on breakthrough.&lt;/p&gt;
&lt;p&gt;For this simple task, we need to know the size of USB_DESCRIPTOR_TYPE_STRING,
but that seems really elusive. I do see a definition in some cherryusb directory,
but that seems like another example. After compiling, I see a binary match
in libcherryusb.a in our build directory! This is the danger of my random spelunking
through this process&amp;hellip;I haven&amp;rsquo;t looked at what steps are being performed during
the build yet. But clearly we&amp;rsquo;re building and linking some cherryusb library.
Now, this SDK seems really mashed together, so when I do a search for cherry usb,
lo and behold, we run into this: &lt;a href=&#34;https://github.com/sakumisu/CherryUSB&#34;&gt;https://github.com/sakumisu/CherryUSB&lt;/a&gt;
This is awesome, and it has docs! Once again, not in my native language&amp;hellip; :(
But code is universal, so we will continue to plow ahead.&lt;/p&gt;
&lt;p&gt;Doing a bit of research, there is a random post on Reddit saying CherryUSB might
be a fork of &lt;a href=&#34;https://github.com/hathach/tinyusb&#34;&gt;TinyUSB&lt;/a&gt;. Looking at the repos, they do seem similar, but with
different MCUs supported. But most importantly to me, there is documentation
in my native English language. We will add &amp;ldquo;figure out differences between
TinyUSB and CherryUSB&amp;rdquo; to the to do list, and soldier on.&lt;/p&gt;
&lt;p&gt;Next up, let&amp;rsquo;s clear a few recent items off the to do list. We&amp;rsquo;ll figure out
that &amp;ldquo;ep&amp;rdquo; question. Looking at &lt;code&gt;cdc_acm_template.c&lt;/code&gt;, we&amp;rsquo;ll see &amp;rsquo;ep&amp;rsquo; stands
for endpoint. It&amp;rsquo;s how we&amp;rsquo;re setting up the &amp;ldquo;callbacks&amp;rdquo; for folks used to
high level programming, or &amp;ldquo;ISR&amp;rdquo; for low level. We should also probably understand
a bit more on how USB works. We are creating a USB &amp;ldquo;device&amp;rdquo;&amp;hellip;aha&amp;hellip;this is the
d in &amp;ldquo;usbd&amp;rdquo;&amp;hellip;daemon is clearly too high level a term for what we are doing.
The device is USB class CDC, with a subclass of ACM. From there, we&amp;rsquo;re on our
own. With this, my mind drifts back to &lt;code&gt;examples/peripherals/usbdev&lt;/code&gt;. What&amp;rsquo;s
in there?&lt;/p&gt;
&lt;p&gt;It looks like there is a &lt;code&gt;usbd_cdc_acm&lt;/code&gt; there. I wonder if that is an even
simpler example. If so, we could look at the differences and get a better
understanding of what Sipeed has done. Let&amp;rsquo;s take a look at that code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;board_init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_data_send_with_dtr_test&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_mtimer_delay_ms&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Well, that&amp;rsquo;s dead simple! We send some data, then wait 500ms. What are we sending?
This cdc_acm_data_send_with_dtr_test function isn&amp;rsquo;t in this file, but it is
in &lt;code&gt;cdc_acm_template.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_data_send_with_dtr_test&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (dtr_enable) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ep_tx_busy_flag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_ep_start_write&lt;/span&gt;(CDC_IN_EP, write_buffer, &lt;span style=&#34;color:#ae81ff&#34;&gt;2048&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (ep_tx_busy_flag) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We need to find out what&amp;rsquo;s in write_buffer, which is initialized slightly above
this function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_init&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; data[&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; { &lt;span style=&#34;color:#ae81ff&#34;&gt;0x31&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x32&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x33&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x34&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x35&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x36&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x37&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x38&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x39&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0x30&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;memcpy&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;write_buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;], data, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;memset&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;write_buffer[&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;], &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2038&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;man ascii&lt;/code&gt; tells us this is &amp;ldquo;1234567890&amp;rdquo; followed by 2038 &amp;ldquo;a&amp;quot;s. Well, that&amp;rsquo;ll
be pretty obvious. Let&amp;rsquo;s run this one.  &lt;code&gt;make CHIP=bl616 BOARD=bl616dk&lt;/code&gt; works,
and I can load it with &lt;code&gt;make flash CHIP=bl616 BOARD=bl616dk COMX=/dev/ttyACM0&lt;/code&gt;.
It works! And wow, that&amp;rsquo;s a lot of output. But it&amp;rsquo;s not &amp;ldquo;Hello world!&amp;rdquo;, and we
haven&amp;rsquo;t written any code yet, so that&amp;rsquo;s just not acceptable. But this is simple
enough, let&amp;rsquo;s get writing.&lt;/p&gt;
&lt;h2 id=&#34;hello-world-finally&#34;&gt;Hello world! Finally&lt;/h2&gt;
&lt;p&gt;Quick recap of our understanding at this point. We&amp;rsquo;ve identified that there
is a simple Bouffalo Labs example for a USB Device. This device implements
the CDC ACM protocol with the help of the CherryUSB library (similar to TinyUSB).
The example outputs data, so we can use it for our example. Time to &lt;em&gt;write&lt;/em&gt;
code&amp;hellip;finally!&lt;/p&gt;
&lt;p&gt;The example has a fairly boilerplace cdc_acm_template.c file that they choose
not to touch. It&amp;rsquo;s still doing some plumbing work, some of which we don&amp;rsquo;t
understand yet. For instance, what is &amp;ldquo;dtr&amp;rdquo;? Seems like another to do list item,
but we don&amp;rsquo;t need to understand it yet.&lt;/p&gt;
&lt;p&gt;For this Hello World, I&amp;rsquo;d like to avoid touching or understanding that file
too much, but I&amp;rsquo;d like an easy way to pass my own data from &lt;code&gt;main.c&lt;/code&gt; to it
to display. Currently, we can&amp;rsquo;t do that. So let&amp;rsquo;s copy the function
&lt;code&gt;cdc_acm_data_send_with_dtr_test&lt;/code&gt; and make a new one that can accept the
data to display:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_data_send_with_dtr&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; data_len )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (dtr_enable) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ep_tx_busy_flag &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;usbd_ep_start_write&lt;/span&gt;(CDC_IN_EP, data, data_len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (ep_tx_busy_flag) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Easy enough - I&amp;rsquo;ve simply added &lt;code&gt;data&lt;/code&gt; and &lt;code&gt;data_len&lt;/code&gt; parameters and used
those instead of the globals in the file. Now let&amp;rsquo;s setup our own write buffer
in main.c and declare the new function at the top of &lt;code&gt;main.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;extern&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_init&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;extern&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_data_send_with_dtr&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;buffer_init&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX &lt;span style=&#34;color:#66d9ef&#34;&gt;uint8_t&lt;/span&gt; write_buffer_main[&lt;span style=&#34;color:#ae81ff&#34;&gt;2048&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, I have no idea yet what &lt;code&gt;USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX&lt;/code&gt; is. Clearly
we&amp;rsquo;re telling the compiler this isn&amp;rsquo;t a cacheable variable, and it has special
alignment requirements, but what exactly? No idea. Another to do list item.&lt;/p&gt;
&lt;p&gt;Eventually, it would be nice to have a &lt;code&gt;printf&lt;/code&gt;-like function, but we don&amp;rsquo;t
have time for that. We&amp;rsquo;ll get part of the way there with an initialization
function that takes a C string and copies it to our buffer. We can&amp;rsquo;t use the
string directly, because it&amp;rsquo;s not &lt;code&gt;USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;buffer_init&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;char&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;data) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; data_len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;ssize_t&lt;/span&gt; inx &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; data[inx]; inx&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    write_buffer_main[inx] &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; data[inx];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (data[inx]) data_len&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; data_len;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We return the data length because it&amp;rsquo;s pretty easy to find here, so let&amp;rsquo;s save
the manual counting. And with that, we can change &lt;code&gt;main()&lt;/code&gt; to a more proper
&amp;lsquo;Hello world!\n&amp;rsquo;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;board_init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32_t&lt;/span&gt; data_len &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;buffer_init&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Hello world!&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\r\n&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;cdc_acm_data_send_with_dtr&lt;/span&gt;(write_buffer_main, data_len);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_mtimer_delay_ms&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;2000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;make CHIP=bl616 BOARD=bl616dk&lt;/code&gt; and &lt;code&gt;make flash CHIP=bl616 BOARD=bl616dk COMX=/dev/ttyACM0&lt;/code&gt;
later, reinsert the device, run &lt;code&gt;screen /dev/ttyACM0 2000000&lt;/code&gt; and we see the following!&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Hello world!
Hello world!
Hello world!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that I changed the delay, so we see a new message appear every 2 seconds.
The completed code so far can be found here: &lt;a href=&#34;https://github.com/elerch/bouffalo_sdk/tree/e790f5fa86c40f2a788c78a5dbdec0ccfacf6209/examples/peripherals/usbdev/usbd_cdc_acm&#34;&gt;https://github.com/elerch/bouffalo_sdk/tree/e790f5fa86c40f2a788c78a5dbdec0ccfacf6209/examples/peripherals/usbdev/usbd_cdc_acm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Our current to do list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What’s the difference between Bouffalo Lab&amp;rsquo;s SDK and Sipeed’s repo?&lt;/li&gt;
&lt;li&gt;Avoid binary toolchains&lt;/li&gt;
&lt;li&gt;What&amp;rsquo;s the difference between TinyUSB and CherryUSB?&lt;/li&gt;
&lt;li&gt;(optional) What is this library doing for us? Can we do it ourselves?&lt;/li&gt;
&lt;li&gt;What is going on in &lt;code&gt;cdc_acm_template.c&lt;/code&gt; Specifically what is the &lt;code&gt;dtr&lt;/code&gt; stuff?&lt;/li&gt;
&lt;li&gt;What is USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX?&lt;/li&gt;
&lt;li&gt;Figure out logging output destination&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next task? I think we have a choice. Either:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Learn how to read data&lt;/li&gt;
&lt;li&gt;Simplify our software stack&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For now, we&amp;rsquo;ll enjoy a device that can print Hello World without any debugging
hardware.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Exploring embedded programming with Sipeed m0s (BL616)</title>
      <link>https://emil.lerch.org/exploring-embedded-programming-with-sipeed-m0s-bl616/</link>
      <pubDate>Wed, 12 Apr 2023 00:00:00 +0000</pubDate>
      
      <guid>https://emil.lerch.org/exploring-embedded-programming-with-sipeed-m0s-bl616/</guid>
      <description>&lt;h1 id=&#34;exploring-embedded-programming-with-the-sipeed-m0s-with-the-bl616-microprocessor&#34;&gt;Exploring embedded programming with the Sipeed M0S with the BL616 microprocessor&lt;/h1&gt;
&lt;p&gt;Note: I do not use Amazon affiliate links. The Amazon links below do &lt;strong&gt;NOT&lt;/strong&gt; kick anything back to me.&lt;/p&gt;
&lt;p&gt;This is part of an ongoing series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Part 1: Exploring embedded programming with the Sipeed M0S with the BL616 microprocessor&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./getting-to-hello-world-with-sipeed-m0s-bl616/&#34;&gt;Part 2: Getting to hello world with Sipeed m0s (BL616)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./simplifying-our-tool-chain-first-steps/&#34;&gt;Part 3: Simplifying our tool chain: First steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./simplifying-our-tool-chain-wrap-up/&#34;&gt;Part 4: Simplifying the tool chain: Wrap up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./learning-the-sdk-and-usb-protocol//&#34;&gt;Part 5: Learning the SDK and USB protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;./wrapping-up-our-exploration-a-mini-shell/&#34;&gt;Part 6: Wrapping up our exploration: A mini shell&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have been dabbling in low level programming lately. Late last year, I put together
a Hello World assembly language sample in multiple ISAs to see the difference between
various CPU architectures. To create something a bit more realistic, this included
the use of Linux syscalls, so these samples work but require Linux. The advantage
here is that I was able to understand better how programs actually interface with
the OS (as long as the OS is Linux :) ). This repository is here: &lt;a href=&#34;https://github.com/elerch/assembly-samples&#34;&gt;https://github.com/elerch/assembly-samples&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I also created a &lt;a href=&#34;https://ziglang.org&#34;&gt;zig&lt;/a&gt;-based program to interface with a &lt;a href=&#34;https://www.amazon.com/Hosyond-Display-Self-Luminous-Compatible-Raspberry/dp/B09C5K91H7/ref=sr_1_3?keywords=oled%2Bi2c%2B128x64&amp;amp;th=1&#34;&gt;small OLED display&lt;/a&gt;
I got for Christmas. This was pretty fun, and allowed me to use some new features
in the newest unreleased version of zig to automatically download/build ImageMagick
and compile it into a single static binary that can work with these devices.
There&amp;rsquo;s lots more that can go into &lt;a href=&#34;https://github.com/elerch/ssd1306_oled_display_cli&#34;&gt;this program&lt;/a&gt;, but the exercise really
was about getting closer to the hardware/software interface. It has literally
been decades since I&amp;rsquo;ve had to cross reference &lt;a href=&#34;https://www.digikey.com/htmldatasheets/production/2047793/0/0/1/ssd1306.html&#34;&gt;data sheets&lt;/a&gt; with my code
and worry about things like clock dividers, etc.&lt;/p&gt;
&lt;p&gt;Most recently, I read the book &lt;a href=&#34;https://www.amazon.com/Code-Language-Computer-Hardware-Software/dp/0137909101/ref=sr_1_2?keywords=code&#34;&gt;Code&lt;/a&gt;, which reminded me a lot about my
undergrad classes at Lehigh, and refreshed my memory on how
hardware/firmware/software interact. Indeed, it went beyond some of the classes
I had had, and overall it was a very pleasant read. Highly recommended
book, although it gets pretty dense, so if you&amp;rsquo;re not super into this stuff,
you might want to read until your eyes glaze over, then skip to the last three
chapters or so where things get a bit higher level again.&lt;/p&gt;
&lt;p&gt;Some of the trigger for this is my recent interest in &lt;a href=&#34;https://en.wikipedia.org/wiki/RISC-V&#34;&gt;Risc-V&lt;/a&gt;. I&amp;rsquo;m pretty
excited overall for this instruction set architecture (ISA). Wikipedia
has many more details, but this is an ISA that was developed at University of
California, Bekeley to be an open source ISA. The ISA describes the interface
between software and hardware for CPUs. So now there is an open standard interface,
and hardware designers can create either &lt;a href=&#34;https://github.com/T-head-Semi/openc910&#34;&gt;open&lt;/a&gt; or &lt;a href=&#34;https://www.sifive.com/press/sifive-performance-p550-core-sets-new-standard-as-highest&#34;&gt;proprietary&lt;/a&gt;
CPU designs that adhere to that interface. An open ISA frees developers from
specific vendors that may have &lt;a href=&#34;https://www.reuters.com/technology/softbanks-arm-china-profit-drops-over-90-2022-document-2023-02-16/&#34;&gt;business&lt;/a&gt; &lt;a href=&#34;https://hothardware.com/news/intel-ceo-execution-issues-500m-loss&#34;&gt;issues&lt;/a&gt; or come under
political pressure. Because of this, RISC-V is already &lt;a href=&#34;https://www.espressif.com/en/products/socs&#34;&gt;wildly&lt;/a&gt;
&lt;a href=&#34;https://www.techpowerup.com/298936/report-apple-to-move-a-part-of-its-embedded-cores-to-risc-v-stepping-away-from-arm-isa&#34;&gt;successful&lt;/a&gt;, though only in the embedded world. That said, the
possibility of RISC-V &lt;a href=&#34;https://www.androidauthority.com/android-risc-v-support-3262537/&#34;&gt;phones&lt;/a&gt;, &lt;a href=&#34;https://www.pine64.org/2023/04/10/pinetab-v-and-pinetab2-launch/&#34;&gt;tablets&lt;/a&gt;, &lt;a href=&#34;https://www.tomshardware.com/news/risc-v-laptop-world-first&#34;&gt;and computers&lt;/a&gt;
are on the horizon.&lt;/p&gt;
&lt;p&gt;With this interest, I have been experimenting with real hardware. My first
experiment was using Debian on the &lt;a href=&#34;https://wiki.sipeed.com/hardware/en/lichee/RV/Dock.html&#34;&gt;Sipeed LicheeRV Dock&lt;/a&gt; and shortly
thereafter on the &lt;a href=&#34;https://mangopi.org/mqpro&#34;&gt;MangoPi MQ Pro&lt;/a&gt;. These are both based on the
&lt;a href=&#34;https://www.allwinnertech.com/uploads/pdf/2021070515231402.pdf&#34;&gt;Allwinner D1&lt;/a&gt;, and are therefore nearly identical. This was an
exciting but also somewhat disappointing step. Exciting because wow, Linux on
RISC-V hardware in my house that wasn&amp;rsquo;t &lt;a href=&#34;https://bit-tech.net/news/tech/cpus/sifive-announces-64-bit-15ghz-risc-v-hifive-unleashed-sbc/1/&#34;&gt;crazy expensive&lt;/a&gt;, but
disappointing as I found the performance to be somewhere between the
&lt;a href=&#34;https://www.raspberrypi.com/products/raspberry-pi-zero/&#34;&gt;Raspberry Pi Zero&lt;/a&gt; and the &lt;a href=&#34;https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/&#34;&gt;Raspberry Pi Zero 2&lt;/a&gt;. Ultimately, I
didn&amp;rsquo;t do a lot with it.&lt;/p&gt;
&lt;p&gt;Next up, is the much more exciting &lt;a href=&#34;https://www.kickstarter.com/projects/starfive/visionfive-2&#34;&gt;StarFive VisionFive 2&lt;/a&gt;. With super
early bird discount, I ordered the 8GB version for less than $100 and
was able to get Debian up and running. Now, this is still very early days, requiring
firmware updates, custom linux kernels and the use of a USB to TTL console
cable (Raspberry Pi directions here: &lt;a href=&#34;https://learn.adafruit.com/adafruits-raspberry-pi-lesson-5-using-a-console-cable)&#34;&gt;https://learn.adafruit.com/adafruits-raspberry-pi-lesson-5-using-a-console-cable)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But&amp;hellip;it was up and running, and the performance was much better, clocking in
somewhere between a Raspberry Pi 3 and a Raspberry Pi 4. If we think about this,
the industry, in a matter of less than a year, brought Linux RISC-V performance
up basically 1.5 generations of Raspberry technology. Looking to the future,
in a few days from my writing of this we are expecting Sipeed to allow orders of the
&lt;a href=&#34;https://wiki.sipeed.com/hardware/en/lichee/th1520/lp4a.html&#34;&gt;Lichee Pi 4A&lt;/a&gt;, which promises performance slightly above the Raspberry
Pi 4, still within a year timeframe from the LicheeRV.&lt;/p&gt;
&lt;h2 id=&#34;embedded-programming&#34;&gt;Embedded programming&lt;/h2&gt;
&lt;p&gt;So far, I&amp;rsquo;ve only briefly mentioned embedded. Well, based on my excitement of
RISC-V and my close following of Sipeed in particular, I saw an announcement
of the ability to run &lt;a href=&#34;https://nitter.net/SipeedIO/status/1594326427708497922#m&#34;&gt;Linux on a tiny MCU&lt;/a&gt;. The Sipeed M0S is the
successor to the announcement above, is also able to run Linux, and is really,
really tiny. Here&amp;rsquo;s a couple photos, first of the chip:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2023-04-12/images/BL616_M0S.jpg&#34;&gt;&lt;img src=&#34;./posts/2023-04-12/thumbnails/BL616_M0S.jpg&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then of the chip attached to the dock for development:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2023-04-12/images/M0S_with_dev_dock.jpg&#34;&gt;&lt;img src=&#34;./posts/2023-04-12/thumbnails/M0S_with_dev_dock.jpg&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Keep in mind&amp;hellip;this thing was $4, and can run Linux! Not very well I&amp;rsquo;m sure, but it&amp;rsquo;s possible.
So, I wanted to play around with it, get down to metal, because considering it is
the size of my fingernail, this thing is a beast.&lt;/p&gt;
&lt;p&gt;This has sent me (back) into the world of super-low level programming, which I had
done a long time ago before the Internet really took off. So far, I&amp;rsquo;m having fun
rediscovering this world, but been running into a lot of &amp;ldquo;hey, we assume you already
know &lt;!-- raw HTML omitted --&gt;&amp;rdquo;, or documentation not in my native language, so I&amp;rsquo;m documenting my
journey here.&lt;/p&gt;
&lt;h2 id=&#34;getting-started&#34;&gt;Getting started&lt;/h2&gt;
&lt;p&gt;Pulling this thing out of the box, we have some IO pins, chip, and power/data
in the form of a USB-C port. Cool&amp;hellip;let&amp;rsquo;s plug it in. I&amp;rsquo;m running Linux on my
desktop, so I issue &lt;code&gt;sudo dmesg -w&lt;/code&gt; when I plug it in to watch what the system
does with this thing. I&amp;rsquo;m presented with:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[8691832.938863] usb 1-10.3: new high-speed USB device number 54 using xhci_hcd
[8691833.063072] usb 1-10.3: config 1 interface 0 altsetting 0 endpoint 0x83 has an invalid bInterval 0, changing to 7
[8691833.063255] usb 1-10.3: New USB device found, idVendor=349b, idProduct=6160, bcdDevice= 2.00
[8691833.063257] usb 1-10.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[8691833.063258] usb 1-10.3: Product: Bouffalo CDC DEMO
[8691833.063259] usb 1-10.3: Manufacturer: Bouffalo
[8691833.066752] cdc_acm 1-10.3:1.0: ttyACM0: USB ACM device
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;ok, cool. Linux seems happy with this. What the heck is a USB ACM device? I&amp;rsquo;m
used to something like &lt;code&gt;/dev/ttyUSB0&lt;/code&gt; or something, but not this. A couple
searches later, I find this means &amp;ldquo;Abstract Control Model&amp;rdquo;, and was used for
communication devices back in the day, but is common for MCU programming in 2023.
More information can be found here: &lt;a href=&#34;https://rfc1149.net/blog/2013/03/05/what-is-the-difference-between-devttyusbx-and-devttyacmx/&#34;&gt;https://rfc1149.net/blog/2013/03/05/what-is-the-difference-between-devttyusbx-and-devttyacmx/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now I need to figure out how to flash it, so I pull the &lt;a href=&#34;https://github.com/bouffalolab/bouffalo_sdk&#34;&gt;BouffaloLab SDK&lt;/a&gt;.
Later, I found that there is a fork of this repo specific to the &lt;a href=&#34;https://github.com/sipeed/M0S_BL616_example&#34;&gt;M0S&lt;/a&gt;. So,
we&amp;rsquo;ll put investigation of the differences here on the to do list, but at a quick
glance it looks like these two repos have their first common ancestor on March
9th in &lt;a href=&#34;https://github.com/bouffalolab/bouffalo_sdk/commit/9b5177d95e84f40f0bcada57f60ec653e1b458f4&#34;&gt;9b5177d&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;ok, next to build and run. This&amp;hellip;gets tricky as I get a lot of documentation
in a language different from my native language, and I&amp;rsquo;m also not used to this
low level work. I keep seeing references to binary blobs checked into repos,
and it looks like this is mainly a complaint focused on the actual flashing
software. But let&amp;rsquo;s get the code built first.&lt;/p&gt;
&lt;p&gt;For Linux, the environment setup refers to using &lt;a href=&#34;https://github.com/bouffalolab/toolchain_gcc_t-head_linux&#34;&gt;this pre-built toolchain&lt;/a&gt;.
I am not a huge fan of this, but we&amp;rsquo;ll go with it for now and add another item
on our to do list to revisit later. I sure as heck am not going to just run
this directly on my host, even though it theoretically is built from &lt;a href=&#34;https://github.com/p4ddy1/pine_ox64/blob/main/build_toolchain_macos.md&#34;&gt;source much
like this&lt;/a&gt;. Ultimately I would love to just build with Zig and take advantage
of the cross platform capabilities, but again, let&amp;rsquo;s just get something running.&lt;/p&gt;
&lt;p&gt;Docker is my typical tool of choice here, though when I run docker I always run
my docker with &lt;a href=&#34;https://podman.io/&#34;&gt;podman&lt;/a&gt;. Daemonless is nice, but it&amp;rsquo;s also using uid mapping,
so I can just run as root inside the container, and files I touch as root there
are actually my usual user on the host. It helps with a lot of permissions
shenanigans. So with the SDK and the toolchain cloned in sibling directories,
and with my current directory the SDK, I startup a container:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;podman run --rm -it -v ${PWD}:/build -v ${PWD}/../toolchain_gcc_t-head_linux/:/toolchain --device /dev/ttyACM0 debian:bullseye
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note I&amp;rsquo;ve passed my device over to the container, although ultimately, I won&amp;rsquo;t
end up flashing from within the container. I&amp;rsquo;m using &lt;code&gt;debian:bullseye&lt;/code&gt; because
I&amp;rsquo;m mostly familiar with debian, that&amp;rsquo;s what I run on my host, and most SDKs
assume you use Ubuntu, which is based on debian and usually close enough for
things to work. If you&amp;rsquo;re following along at home, note the use of &lt;code&gt;--rm&lt;/code&gt;
means the container will disappear as soon as you leave it, so be careful.&lt;/p&gt;
&lt;p&gt;Eventually, I find the following commands within the container work wonderfully:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;apt update &amp;amp;&amp;amp; apt install -y build-essential
export PATH=/toolchain/bin/:$PATH
cd build/examples/helloworld/
make CHIP=bl616 BOARD=bl616dk
make flash CHIP=bl616 COMX=/dev/ttyACM0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The whole &lt;code&gt;COMX&lt;/code&gt; thing tripped me up a bit. First, it&amp;rsquo;s Windows terminology.
Second, what&amp;rsquo;s with the &lt;code&gt;X&lt;/code&gt;? The make commands invoke CMake, and when I screwed
up the make commands I often had to delete the &lt;code&gt;build&lt;/code&gt; directory inside the
&lt;code&gt;helloworld&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re built! Now to get this the code onto the device. After some trial and error,
I find the following procedure to work:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Hold down the &lt;code&gt;boot&lt;/code&gt; button&lt;/li&gt;
&lt;li&gt;Plug into USB&lt;/li&gt;
&lt;li&gt;Release the &lt;code&gt;boot&lt;/code&gt; button&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;make flash CHIP=bl616 COMX=/dev/ttyACM0&lt;/code&gt; from the &lt;strong&gt;host&lt;/strong&gt; while
inside examples/helloworld in the SDK&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After doing this, you can unplug/plug in the device, and magically it&amp;rsquo;ll work!&lt;/p&gt;
&lt;p&gt;Umm..no, of course not. The first time I did this, I noticed that after plugging
it back in, my &lt;code&gt;/dev/ttyACM0&lt;/code&gt; is totally gone. My theory is when the device is
shipped, it&amp;rsquo;s shipped with firmware that allows you to upload firmware without
pressing the boot button on power on. To get &lt;code&gt;/dev/ttyACM0&lt;/code&gt; back, you need to
hold down the boot button while plugging it in. Then you can release the button.&lt;/p&gt;
&lt;p&gt;I believe that the boot button simply toggles which firmware is run at startup.
With the boot button we get the &amp;ldquo;firmware loading firmware&amp;rdquo;, and without, we get
&amp;ldquo;whatever I compiled and uploaded&amp;rdquo; firmware to run. The helloworld firmware
just prints out &amp;ldquo;hello world&amp;rdquo; somewhere. It&amp;rsquo;s our job to find out where. And
Linux isn&amp;rsquo;t showing anything. Being impatient, I don&amp;rsquo;t have time for any of this
detective work at the moment. So, to do list item #3, and let&amp;rsquo;s see if we can
do the universal Internet of Things version of hello world, otherwise known as
&amp;ldquo;get to blinky&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;So, a few short searches later, I stumble on &lt;a href=&#34;https://gist.github.com/hndrbrm/73713e2c33fb193685863ecde3440df7&#34;&gt;this gist&lt;/a&gt; (thanks some
random Internet person!). So, I give a quick glance at the code, it looks
reasonable, and I build/flash it to the device. Success! I now have two LEDs
built into the dev board alternating every 500ms. Current todo list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What&amp;rsquo;s the difference between Bouffalo Labs&amp;rsquo; SDK and Sipeed&amp;rsquo;s repo?&lt;/li&gt;
&lt;li&gt;Avoid binary toolchains&lt;/li&gt;
&lt;li&gt;Figure out what&amp;rsquo;s up with hello world&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We&amp;rsquo;ll take these in order, so first up:&lt;/p&gt;
&lt;h2 id=&#34;figure-out-whats-up-with-hello-world&#34;&gt;Figure out what&amp;rsquo;s up with hello world&lt;/h2&gt;
&lt;p&gt;Hello world is going to be outputting somewhere. That&amp;rsquo;s kind of it&amp;rsquo;s entire
purpose in life, after all. There are two reasonable places (maybe a third?)
it could output, and my assumption going into the world of &amp;ldquo;hello world&amp;rdquo;
was that it would be the first. However, that&amp;rsquo;s clearly not correct:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;/dev/ttyACM0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The serial console&lt;/li&gt;
&lt;li&gt;(??) JTag. I don&amp;rsquo;t know too much about JTag, so if it&amp;rsquo;s that, we have another
item for the todo list&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you recall, on flashing the example, my ACM device disappeared. So, we could
be at the serial console. Where is that exactly? Time for some digging. We&amp;rsquo;ll
need the following for our detective work.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/bouffalolab/bouffalo_sdk&#34;&gt;SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dl.sipeed.com/Maix-Zero/M0S/M0S/2_Schematic/M0S_Schematic_V1.0.pdf&#34;&gt;M0S Schematic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dl.sipeed.com/Maix-Zero/M0S/M0S_Dock/2_Schematic/M0S-Dock_Schematic_V1.0.pdf&#34;&gt;M0S Dock Schematic&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because the hello world example is using a bunch of logging statements, I do
have some concerns it might be outputting to JTag. So let&amp;rsquo;s find a different
example that&amp;rsquo;s definitely not using that. Shell looks fun! So we&amp;rsquo;ll go into
&lt;code&gt;examples/shell&lt;/code&gt; and see what&amp;rsquo;s there. There&amp;rsquo;s an OS and a non-OS version, but
we want to remove abstractions and learn this ground up, so we&amp;rsquo;ll go with
non-OS.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s flash it for fun and see what we get.  Well, nothing. It&amp;rsquo;s the same as
hello world. No device in &lt;code&gt;/dev&lt;/code&gt; for screen or anything to attach to. But this
one is much more likely to be intended for use on a serial console. Looking at
the source code, we find &lt;a href=&#34;https://github.com/bouffalolab/bouffalo_sdk/blob/master/examples/shell/shell_no_os/main.c#L24-L28&#34;&gt;it&amp;rsquo;s clearly trying to use uart0&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; ch;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;board_init&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    uart0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_device_get_by_name&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;uart0&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cool. But we need to know where uart0 physically is so we can connect to it.
Right now, I assume it&amp;rsquo;ll be one of the I/O pins we see hanging off the end of
the dock. &lt;code&gt;grep -r board_init&lt;/code&gt; from the base of the SDK brings up too much noise,
but doing another &lt;code&gt;grep -r uart0&lt;/code&gt; yields a few interesting bits. Specifically,
There is a &lt;code&gt;drivers/lhal/config/bl616/device_table.c&lt;/code&gt; file that looks interesting.
Looking at that file ultimately doesn&amp;rsquo;t give me too much&amp;hellip;but let&amp;rsquo;s put a &lt;a href=&#34;https://github.com/bouffalolab/bouffalo_sdk/blob/master/drivers/lhal/config/bl616/device_table.c#L36-L40&#34;&gt;pin
in that one&lt;/a&gt; because it may come in handy later. What looks really interesting
is &lt;code&gt;bsp/board/bl616dk/board.c&lt;/code&gt;, so let&amp;rsquo;s take a peek there. A few searches through
that file looking for uart0, and &lt;a href=&#34;https://github.com/bouffalolab/bouffalo_sdk/blob/master/bsp/board/bl616dk/board.c#L184-L205&#34;&gt;pay dirt&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;console_init&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; bflb_device_s &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;gpio;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    gpio &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_device_get_by_name&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;gpio&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_gpio_uart_init&lt;/span&gt;(gpio, GPIO_PIN_21, GPIO_UART_FUNC_UART0_TX);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_gpio_uart_init&lt;/span&gt;(gpio, GPIO_PIN_22, GPIO_UART_FUNC_UART0_RX);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; bflb_uart_config_s cfg;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg.baudrate &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2000000&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg.data_bits &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UART_DATA_BITS_8;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg.stop_bits &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UART_STOP_BITS_1;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg.parity &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UART_PARITY_NONE;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg.flow_ctrl &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg.tx_fifo_threshold &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    cfg.rx_fifo_threshold &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    uart0 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_device_get_by_name&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;uart0&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_init&lt;/span&gt;(uart0, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;cfg);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;bflb_uart_set_console&lt;/span&gt;(uart0);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ok - so UART transmit is on GPIO pin 21, and receive is on pin 22. We also have
uart speed, parity, data bits, flow control. Everything we could ever want. But
where is GPIO pin 21 and 22? Let&amp;rsquo;s take a look at some schematics. There are
two sets of schematics. Ultimately we&amp;rsquo;re looking for where to plug a TTL to USB
device into the DOCK, so we&amp;rsquo;ll want to look at the dock schematic from the
link above. Doing so, we can find GPIO21 and GPIO22 sitting in the bottom
right of the schematic where it pictures the BL616 module.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2023-04-12/DockSchematicModule.png&#34;&gt;&lt;img src=&#34;./posts/2023-04-12/DockSchematicModule.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next, we have to find out where RX0 and TX0 go. Looks like that&amp;hellip;is right here:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2023-04-12/J4.png&#34;&gt;&lt;img src=&#34;./posts/2023-04-12/J4.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So we&amp;rsquo;re looking for J4. It&amp;rsquo;s not clear from the schematic or the data sheet where
J4 is, but J4 has TX, RX and a Ground, so three holes. The board is pretty tiny,
so it&amp;rsquo;s not a lot of real estate to search. The answer seems to be &amp;ldquo;right next to
the USB plug&amp;rdquo;. I don&amp;rsquo;t really want to pull out a soldering iron in this process,
so I have three questions at this point:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Can I just stick wires into these holes in the board?&lt;/li&gt;
&lt;li&gt;Can I move to other GPIO pins?&lt;/li&gt;
&lt;li&gt;Can I route communications directly through the USB port I&amp;rsquo;m already plugged into?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The third option seems especially relevant given I &lt;em&gt;started&lt;/em&gt; this journey with
a &lt;code&gt;/dev/ttyACM0&lt;/code&gt; device. 2,617 words later though, let&amp;rsquo;s visit this in another
post.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Gitea actions and build badges</title>
      <link>https://emil.lerch.org/gitea-actions-and-build-badges/</link>
      <pubDate>Sun, 26 Feb 2023 14:59:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/gitea-actions-and-build-badges/</guid>
      <description>&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; With Gitea 1.22.0, build badge support is now &lt;a href=&#34;https://docs.gitea.com/usage/actions/badge&#34;&gt;built in&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&#34;gitea-actions-and-build-badges&#34;&gt;Gitea actions and build badges&lt;/h1&gt;
&lt;p&gt;Gitea actions, at the time of this writing, are new. A
&lt;a href=&#34;https://blog.gitea.io/2022/12/feature-preview-gitea-actions/&#34;&gt;feature preview blog post&lt;/a&gt;
was released in December 2022, and as I write this, Gitea 1.19 RC0 was only published
a few days ago in late February 2023.&lt;/p&gt;
&lt;p&gt;There is much work to be done to make the feature complete, but I am excited to
adopt it as quickly as possible. I have been interested in simplifying my
self-hosted setup and removing &lt;a href=&#34;https://drone.io&#34;&gt;Drone.io&lt;/a&gt;
due to the &lt;a href=&#34;https://woodpecker-ci.org/faq#why-is-woodpecker-a-fork-of-drone-version-08&#34;&gt;licensing weirdness&lt;/a&gt;.
I also never automated the reconnection needed between drone and Gitea
after a host server reboot/power outage/docker upgrade. Also, I have been
impressed with the success of &lt;a href=&#34;https://docs.github.com/en/actions&#34;&gt;GitHub Actions&lt;/a&gt;
and its adoption in other systems like &lt;a href=&#34;https://aws.amazon.com/blogs/devops/using-github-actions-with-amazon-codecatalyst/&#34;&gt;Amazon CodeCatalyst&lt;/a&gt;.
Gitea is the third system I&amp;rsquo;ve used/been involved with that has embraced the
actions format, so it has some industry momentum.&lt;/p&gt;
&lt;h2 id=&#34;feature-state-of-gitea-actions&#34;&gt;Feature state of Gitea actions&lt;/h2&gt;
&lt;p&gt;Gitea actions, as it says in the blog post, is in a fairly preliminary state.
Here is a short list of what is &lt;strong&gt;NOT&lt;/strong&gt; in Gitea 1.19 RC0, nor do I believe
these will be in 1.19:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule&#34;&gt;Cron jobs&lt;/a&gt;: &lt;a href=&#34;https://github.com/go-gitea/gitea/pull/22751&#34;&gt;PR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An API&lt;/li&gt;
&lt;li&gt;Build badges&lt;/li&gt;
&lt;li&gt;Artifacts&lt;/li&gt;
&lt;li&gt;Services&lt;/li&gt;
&lt;li&gt;Documentation (for the most part, you can follow GitHub actions documentation though)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/go-gitea/gitea/issues/22958&#34;&gt;Pull request support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More details are available on the &lt;a href=&#34;https://github.com/go-gitea/gitea/issues/13539&#34;&gt;master issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Gitea is also riding a fine line of &amp;ldquo;this is gitea, not github&amp;rdquo;, and &amp;ldquo;we want to
allow users to copy over their github actions workflows&amp;rdquo;. Some differences to be
aware of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gitea actions are in the folder &lt;code&gt;.gitea/workflows&lt;/code&gt; rather than &lt;code&gt;.github/workflows&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Gitea actions &lt;a href=&#34;https://github.com/go-gitea/gitea/issues/13539&#34;&gt;uses&lt;/a&gt; property
looks at gitea.com, not github.com. Gitea has copied over all GitHub owned
actions, but GitHub marketplace actions won&amp;rsquo;t work without fully specifying
the URL&lt;/li&gt;
&lt;li&gt;Based on the two above, you might assume that everything has been renamed. But
for compatibility, that&amp;rsquo;s not true. So in your workflow yaml, you still reference
&lt;code&gt;${{ github.&amp;lt;property&amp;gt; }}&lt;/code&gt;. I suspect they&amp;rsquo;ll probably eventually allow use
of either GitHub or gitea, but that&amp;rsquo;s just me gazing into a crystal ball.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Secrets, happily, work just great, and all the other pieces are there, so for a simple,
&amp;ldquo;I&amp;rsquo;m not going to push this thing too hard&amp;rdquo;, it works.&lt;/p&gt;
&lt;h2 id=&#34;bugs-and-other-weirdness&#34;&gt;Bugs and other weirdness&lt;/h2&gt;
&lt;p&gt;Gitea actions build runner is still pretty new. It doesn&amp;rsquo;t support &lt;a href=&#34;https://gitea.com/gitea/act_runner/issues/8&#34;&gt;running as
a container&lt;/a&gt;, but I don&amp;rsquo;t seem
to have a problem running it in a container as long as I mount the docker
socket.&lt;/p&gt;
&lt;p&gt;However, I did find that trying to use ${{ github.server_url }} in my workflow
files resulted in an empty string. I can see the code that sets that property,
and my configuration is set properly as far as I can tell, so I&amp;rsquo;m not sure
if this is a bug or a user error. The environment variable &lt;code&gt;$GITHUB_SERVER_URL&lt;/code&gt;
&lt;strong&gt;is&lt;/strong&gt; set though, so I&amp;rsquo;ve just used that.&lt;/p&gt;
&lt;p&gt;So for a minimal setup, keep the above in mind, don&amp;rsquo;t push the boundaries too
far, and all is good. But&amp;hellip;I really wanted build badge support. Coming from
drone.io this is a feature I had. GitHub has it. Pretty much every system has
it. I&amp;rsquo;m sure Gitea will eventually include it. But right now, no such thing
exists. So, time to code a workaround.&lt;/p&gt;
&lt;h2 id=&#34;getting-build-badge-support&#34;&gt;Getting build badge support&lt;/h2&gt;
&lt;p&gt;A couple things, before getting started.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;m looking to enable build badge support for my public repos. I don&amp;rsquo;t really
care about private repos&lt;/li&gt;
&lt;li&gt;I am confident that this will be built in to gitea proper, so a hacky short-term
workaround is fine.&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s no API support&lt;/li&gt;
&lt;li&gt;I don&amp;rsquo;t want anything elaborate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So with that in mind, I&amp;rsquo;ve turned to the following services to build something
in a couple hours:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://shields.io/&#34;&gt;Shields.io&lt;/a&gt; to provide the imagery&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://workers.cloudflare.com/&#34;&gt;Cloudflare workers&lt;/a&gt; and Cloudflare generally
for DNS and proxy support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My plan was relatively simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Setup a custom subdomain &lt;a href=&#34;https://actions-status.lerch.org&#34;&gt;actions-status.lerch.org&lt;/a&gt;
that has all requests handled by Cloudflare workers&lt;/li&gt;
&lt;li&gt;Use a worker to probe the gitea interface, scrape the HTML (YUCK! But there&amp;rsquo;s no API&amp;hellip;),
and determine the latest build status&lt;/li&gt;
&lt;li&gt;Build the appropriate URL to shields.io and fetch the output&lt;/li&gt;
&lt;li&gt;Return the output to the caller&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two hours later, I came up with this solution, which works pretty well. One thing
I noticed it doesn&amp;rsquo;t do is differentiate between a canceled workflow and a
failed one. It turns out, the HTML is identical on the summary screen, so another
call would be needed to determine &amp;ldquo;canceled&amp;rdquo;, and covering that edge case didn&amp;rsquo;t
seem all that necessary.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in a similar solution, make sure you change &lt;code&gt;git.lerch.org&lt;/code&gt;
below. Also, the badge styling may not be your cup of tea so to speak, so you
may want to update the lines where the &lt;code&gt;badgeUrl&lt;/code&gt; variable is set starting at
line 44.&lt;/p&gt;
&lt;p&gt;I kind of like using the gitea logo on the badge. I feel kind of happy that I&amp;rsquo;m
likely the first person to use a gitea badge build based on a gitea actions
run on a project. You can see the badge in action on my &lt;a href=&#34;https://git.lerch.org/lobo/aws-sdk-for-zig/&#34;&gt;aws-sdk-for-zig
project page&lt;/a&gt; or it&amp;rsquo;s &lt;a href=&#34;https://github.com/elerch/aws-sdk-for-zig&#34;&gt;GitHub
mirror&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt; This was done with &lt;a href=&#34;https://github.com/go-gitea/gitea/releases/tag/v1.19.0-rc0&#34;&gt;Gitea 1.19 RC0&lt;/a&gt;.
Because HTML returned is so fragile, it&amp;rsquo;s &lt;del&gt;possible&lt;/del&gt;probable that the web
page scraping breaks with other versions. With any luck, an API will be available
before that happens, but who knows. To save you reading the code, the technique
used is to visit the URL &lt;code&gt;https://git.lerch.org/&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;/actions?state=closed[&amp;amp;workflow=&amp;lt;workflow&amp;gt;.yaml]&lt;/code&gt;.
When the html is returned, the code finds the first instance of an element
with a &lt;code&gt;commit-status&lt;/code&gt; CSS class, then captures whether it should be red or green
(these are the names of two other CSS classes used). Green is success, red
is either failed or canceled. All this fragility is in the &lt;code&gt;parseResponse&lt;/code&gt;
function starting line 19 below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;request&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;env&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getOrigin&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;components&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;components&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;owner&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;components&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;components&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;workflow&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;components&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;components&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;] &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;workflow&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;workflow&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;endsWith&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;.yaml&amp;#39;&lt;/span&gt;)){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#a6e22e&#34;&gt;workflow&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;workflow&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;.yaml&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;owner: &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;owner&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;repo: &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;workflow: &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;workflow&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;workflow length: &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;workflow&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;?state=closed&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;workflow&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`&amp;amp;workflow=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;workflow&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`https://git.lerch.org/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;owner&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;repo&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/actions&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parseResponse&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;regexp&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;/commit-status.*icon (green|red)/&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;match&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;match&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;regexp&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;match&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;match&lt;/span&gt;) { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;textStatus&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;match&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;textStatus&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;green&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;succeeded&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;textStatus&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;red&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;failed&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;unexpected action icon status: &amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;textStatus&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; { &lt;span style=&#34;color:#a6e22e&#34;&gt;pathname&lt;/span&gt; } &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;URL&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;request&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;url&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;components&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pathname&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;split&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;components&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;||&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;components&lt;/span&gt;[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Response&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;usage: /&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;[/workflow]\nNOTE: workflow does not require .yaml extension&amp;#34;&lt;/span&gt;, {&lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;404&lt;/span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;origin&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getOrigin&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;components&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;origin&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;originResponse&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;origin&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;originResponse&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;200&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;originResponse&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;parseResponse&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;await&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;originResponse&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;text&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;let&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;badgeUrl&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;https://img.shields.io/badge/build-&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;badgeUrl&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;unknown-blueviolet&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;badgeUrl&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;badgeUrl&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;succeeded&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;success&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;red&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;badgeUrl&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;?logo=gitea&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;console&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;badgeUrl&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fetch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;badgeUrl&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;catch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Response&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;stack&lt;/span&gt;, { &lt;span style=&#34;color:#a6e22e&#34;&gt;status&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;500&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>First thoughts on Zig</title>
      <link>https://emil.lerch.org/first-thoughts-on-zig/</link>
      <pubDate>Mon, 18 Jan 2021 12:23:18 -0700</pubDate>
      
      <guid>https://emil.lerch.org/first-thoughts-on-zig/</guid>
      <description>&lt;h1 id=&#34;first-thoughts-on-zig&#34;&gt;First thoughts on Zig&lt;/h1&gt;
&lt;p&gt;I encountered &lt;a href=&#34;https://www.ziglang.org&#34;&gt;Zig&lt;/a&gt; a while ago and it intrigued me.
It&amp;rsquo;s intrigued me enough to write a
&lt;a href=&#34;https://github.com/elerch/event-processor&#34;&gt;small utility program for personal use&lt;/a&gt;
and now an &lt;a href=&#34;https://github.com/elerch/aws-sdk-for-zig&#34;&gt;SDK for AWS&lt;/a&gt;, with
some contributions to upstream projects along the way. All told I&amp;rsquo;ve written
more than 10k lines of code in the language (current net code count is just shy,
but I&amp;rsquo;ve done a lot of refactoring!). My current opinion is that zig, for me
is a great blend of high level/low level with a focus on efficiency. My intent
at this time is to use zig whenever possible.&lt;/p&gt;
&lt;p&gt;If I can try to summarize the goals of the language, it would be this:&lt;/p&gt;
&lt;p&gt;Provide a &lt;strong&gt;systems programming&lt;/strong&gt; programming language that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is better defined than C&lt;/li&gt;
&lt;li&gt;Has first class support for cross-compilation (both OS and processor types)&lt;/li&gt;
&lt;li&gt;Provides a high degree of safety&lt;/li&gt;
&lt;li&gt;Can be used for embedded (bare metal/no OS) development&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Provide a &lt;strong&gt;general purpose&lt;/strong&gt; programming language that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Doesn&amp;rsquo;t need garbage collection&lt;/li&gt;
&lt;li&gt;Is simple&lt;/li&gt;
&lt;li&gt;Generates small/fast executables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When working in the language it sometimes feels like I&amp;rsquo;m programming in
&lt;a href=&#34;https://golang.org&#34;&gt;go&lt;/a&gt; and sometimes in &lt;a href=&#34;...&#34;&gt;c&lt;/a&gt;. I like the &amp;ldquo;simplicity&amp;rdquo;
of the language, but more on that later.&lt;/p&gt;
&lt;p&gt;Should you use it? Probably not&amp;hellip;but that&amp;rsquo;s due to its one fatal flaw right
now, which is that it&amp;rsquo;s just not baked yet. At the time of this writing,
it is at version 0.8.0 and sees hundreds of commits per week. Some of these
are breaking changes, and depending on your needs, some features may be
missing or broken. Documentation of the standard library is a lot of &amp;ldquo;go read
the source code&amp;rdquo;. Parts of the language and library don&amp;rsquo;t follow naming
conventions or idioms that are still evolving and changing in the language.&lt;/p&gt;
&lt;h2 id=&#34;comparison-to-other-languages&#34;&gt;Comparison to other languages&lt;/h2&gt;
&lt;p&gt;I mentioned above it sometimes feels like go and sometimes like c. I think
spiritually these are zig&amp;rsquo;s closest languages, and I&amp;rsquo;ve heard zig described
as one of the languages that are trying to be &amp;ldquo;a better C&amp;rdquo;. If we remove
garbage collection from go, we get to something pretty close to zig. So, I think
these two languages are a good place to start:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Go&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Like go, zig is a simple language. Its &lt;a href=&#34;https://ziglang.org/documentation/0.8.0/#Grammar&#34;&gt;grammar&lt;/a&gt;
is currently 554 lines. If you understand go, you can get to zig (mostly) by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removing closures&lt;/li&gt;
&lt;li&gt;Removing garbage collection&lt;/li&gt;
&lt;li&gt;Removing goroutines&lt;/li&gt;
&lt;li&gt;Removing tabs ;-)&lt;/li&gt;
&lt;li&gt;Adding spaces&lt;/li&gt;
&lt;li&gt;Adding a sane error handling mechanism&lt;/li&gt;
&lt;li&gt;Adding &amp;ldquo;generics&amp;rdquo; (more later)&lt;/li&gt;
&lt;li&gt;Adding first class embedded development&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://ziglang.org/documentation/0.8.0/#for&#34;&gt;For loops&lt;/a&gt; in zig reminds me of
the &lt;a href=&#34;https://tour.golang.org/moretypes/16&#34;&gt;range-for&lt;/a&gt; loop in go. However, zig
uses the same capture syntax from for loops in its while loops, if statements
and switches, which gives a nice consistency to the language. Declaring public
things in zig is explicit, unlike the capitalization-based concept of go.
Personally I like the explicit way of doing things, but I see the rationale of
go here. zig also disallows tabs in the source code, which is also my jam, but
it certainly bothers some.&lt;/p&gt;
&lt;p&gt;Also worth noting is binary size. As of go 1.15, a hello world, even without CGO
Enabled, is just shy of 2MB. there is an &lt;a href=&#34;https://github.com/golang/go/issues/6853&#34;&gt;epic
issue&lt;/a&gt; talking about this. zig&amp;rsquo;s
hello world, built in default debug mode is 570k.  Switching to release mode
will give you a 69k binary that when stripped will be approximately 2k. While
memory utilization is of course variable, I&amp;rsquo;ve directly converted a go program
to zig and easily seen a 10x improvement on that front. When you have to
allocate/deallocate memory yourself, you&amp;rsquo;ll be a lot more careful. A notable
advantage of go over zig is that the compiler is super-fast in comparison.&lt;/p&gt;
&lt;p&gt;Of note, since zig is pretty new, it&amp;rsquo;s important to be able to interface with
C libraries. Zig makes this more or less seamless, and since
&lt;a href=&#34;https://andrewkelley.me/post/zig-cc-powerful-drop-in-replacement-gcc-clang.html&#34;&gt;zig can be used as a drop in replacement for cc&lt;/a&gt;,
this can be done with the same zig download  you use to compile zig code.
In my SDK, I had to work around
&lt;a href=&#34;https://github.com/ziglang/zig/issues/1499&#34;&gt;zig&amp;rsquo;s lack of direct bitfield&lt;/a&gt;
support. The solution here was to simply drop a C file in the middle
of my project and have zig compile it with everything else. Using C from Go
feels like a bolt-on compared to zig, and is much harder.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Like C, zig is focused on portability across operating systems and hardware.
In 0.8.0, it has &lt;a href=&#34;https://ziglang.org/download/0.8.0/release-notes.html#Tier-1-Support&#34;&gt;Tier 1&lt;/a&gt;
support for 8 architectures and 4 Operating Systems in addition to freestanding/
bare metal development. Tier 2 support adds 3 more Operating Systems plus UEFI,
and 3 additional hardware architectures. However, C leaves a lot of underspecified
behavior, which makes a lot of compiler differences. Lack of C bitfield support
in zig is pretty much blocked due to compiler differences stemming from
underspecified behavior in the C language. Of course, complaining about
compiler differences may not be entirely fair as zig only has a single
compiler implementation, but the expectation of a language should be that
the specification prevents a lot of undefined behavior. In zig, Nulls,
must be explicitly allowed, and will be checked. Undefined variables are
possible, but also checked by the compiler to be defined before use. Zig
also adds &lt;code&gt;defer&lt;/code&gt; and &lt;code&gt;errdefer&lt;/code&gt;, which provide the ability to handle a lot
of memory (or other resource) deallocation edge cases. As zig matures, additional
checks are being investigated and added. Most recently, after 0.8.0 a change
was made to detect and prevent unused variables.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rust&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Rust tries to find &amp;ldquo;zero-cost abstractions&amp;rdquo; to provide memory safety in an
object oriented environments. I think of the language as C++, but with memory
safety and without a lot of cruft that C++ started with and has gotten worse
over time. Like zig, cross-compilation of Rust is easy. Like Go, incorporation
of C libraries requires more work than zig. Because Rust has a first-class
concept of memory &amp;ldquo;ownership&amp;rdquo;, allowing Rust to control allocation and
de-allocation of memory. In zig, all memory allocation and deallocation is
explicit. Zig&amp;rsquo;s approach can be painful at times compared to Rust, but this
pain also forces the programmer to consider allocations carefully and in
practice probably reduces the working set of many programs. It also means that
segfaults are part of life, as are memory leaks, although zig test and choosing
the right allocator can help debug leaks.  Rust is much more complicated than
zig, and compilations are much slower. The toolchain is also much heavier, with
image on-disk size of 806MB for docker 1.53-alpine version. Zig&amp;rsquo;s toolchain,
uncompressed on disk weighs in around 300MB by comparison.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Other&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Sorry, other languages, you get bucketed here. Java and C# can join most
of the go discussion, except that these are true object oriented languages,
while zig is procedural. Functional languages have great attributes and I like
them a lot, but they are typically divorced from the hardware they are running
on, while with zig this is decidedly not the case.&lt;/p&gt;
&lt;h2 id=&#34;memory&#34;&gt;memory&lt;/h2&gt;
&lt;p&gt;Memory management, without a garbage collector, is clearly a focus of the
language. Allocations and deallocations are explicit just as in C. However, zig
has an approach that works well for managing deallocations to prevent a lot of
issues. In zig, you can return an error, which feels a little bit like
exceptions in languages like C# or Java. In fact, zig has a keyword &lt;code&gt;try&lt;/code&gt; that
will automatically return an error if an error occurs within the expression,
and a keyword &lt;code&gt;catch&lt;/code&gt; that will allow you to &amp;ldquo;handle&amp;rdquo; an error. To avoid
missing a deallocation for exceptional situations, you can use &lt;code&gt;defer&lt;/code&gt;
statements for deallocation (or tear down for other resources). For situations
where returned memory will be owned by the caller, another keyword &lt;code&gt;errdefer&lt;/code&gt;
can be used. This statement basically says, &amp;ldquo;If an error happens in this
function, deallocate/deinitialize, otherwise the caller is responsible&amp;rdquo;. In
practice, this works really well once you start getting the hang of it. I have,
however, had my share of segfaults and memory leaks in testing before
understanding how to build tests and structure allocators to detect leaks.&lt;/p&gt;
&lt;h2 id=&#34;safety&#34;&gt;safety&lt;/h2&gt;
&lt;p&gt;Zig has an ongoing effort to improve safety, so this section will change a lot
as the language evolves. Right now, there are built in test facilities around
memory leaks. There are checks for buffer overruns and integer overflows. There
are pointer alignment checks, asynchronous checks, and array bounds checks.
Much more is planned, and the project culture is to eliminate as many &amp;ldquo;foot guns&amp;rdquo;
as possible in most scenarios. It is possible to turn checks off in lieu of
performance and executable size, though that isn&amp;rsquo;t recommended. Null values
must be explicit, as are undefined variables.&lt;/p&gt;
&lt;h2 id=&#34;async&#34;&gt;async&lt;/h2&gt;
&lt;p&gt;Async operations are designed to be transparent. Unlike many languages, a
function is free to do its own thing, then suddenly find itself executed
asynchonously. There are some subtle differences between zig, and say, Rust
here in terms of implementation as well, but the philosophy in zig is to
allow a lot of code reuse, and the async approach enables that, as does
its comptime behavior, as I will talk about next.&lt;/p&gt;
&lt;h2 id=&#34;comptime-and-generics&#34;&gt;comptime (and generics)&lt;/h2&gt;
&lt;p&gt;Comptime is the most unique part of the language IMHO. Functions can run at
runtime or at compile time (comptime) &amp;ldquo;without knowing the context&amp;rdquo;. These
quotes are pretty important, because in my experience, compile time code is
super powerful, but as a programmer you absolutely have to consider both
compile and runtime behavior of functions as well as the partial compilation of
functions that result in part compile, part runtime behavior. Used well, the
ability to run arbitrary code at compile time is an incredibly powerful tool.
But understanding how this works and how to take advantage of it is easily the
biggest learning curve of the language. The success of the language, in my
opinion, hinges primarily on two factors. First, zig might be too late. As an
industry, Rust might be &amp;ldquo;good enough&amp;rdquo;, and zig may not get traction as a
result. Secondly, comptime semantics might be too hard to reason about, or
alternatively, might be the killer feature the industry has been waiting for.&lt;/p&gt;
&lt;p&gt;As an example, you may be coding away and ultimately write a function that
starts looping through the fields of an enum because you&amp;rsquo;re doing some light
metaprogramming. My recent example here is using enum values as switches to
a command line program. Anything metaprogramming becomes comptime. First,
you&amp;rsquo;ll get an error you probably don&amp;rsquo;t understand. The solution is to use an
inline for, which you&amp;rsquo;ll eventually figure out, but understanding the compiler
behavior may be beyond some new programmers. This is complicated by the fact
that throwing in some &amp;ldquo;printf debugging&amp;rdquo; won&amp;rsquo;t do you any good because that,
of course, is runtime, so you need to use &lt;code&gt;@compileLog&lt;/code&gt; statements instead,
which can look &lt;strong&gt;really&lt;/strong&gt; weird when doing a &lt;code&gt;zig build&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;The other aspect of this that concerns me is that the system is so powerful
that it may make IDEs unable to assist the programmer. For example, zig has
&lt;code&gt;anytype&lt;/code&gt;, which is kind of equivalent to &lt;code&gt;var&lt;/code&gt; in many languages and is
usually used for function parameters. It basically says, &amp;ldquo;I don&amp;rsquo;t want to
worry about the type of this thing right now - I know what I&amp;rsquo;m doing&amp;rdquo;. anytype
will get replaced with the actual type at compile time, so you don&amp;rsquo;t lose
safety, but in order for an IDE or language server to provide
completion information, the code needs to be fully evaluated, including all
call sites. As a practical matter, I don&amp;rsquo;t believe I&amp;rsquo;ve seen any intelligent
completion based on anytype parameters, only my brain saying &amp;ldquo;I know it&amp;rsquo;s
anytype but the thing I get will have a function foo() on it&amp;rdquo;. To be fair,
my brain needs to handle this with dynamically typed languages like JavaScript
or Python, but zig is typed and it feels inconsistent here.&lt;/p&gt;
&lt;p&gt;The nice thing about comptime, though, is the power it provides (or promises).
There are still gaps here including big ones like the inabillity to allocate memory
at comptime. These plan to be fixed, but even without memory allocation amazing
things are currently possible, like the ability to parse json data at
compile time. This pushes a lot more computation up front and makes executables
much faster as a result. It also allows generics in what is otherwise a
&amp;ldquo;generics-free&amp;rdquo; language, because the return type for a function, for instance,
can be calculated by another function that runs at comptime! It&amp;rsquo;s amazing, and
I&amp;rsquo;ve already overused this several times before ultimately backing down to
go a more pedestrian path.&lt;/p&gt;
&lt;h2 id=&#34;feel&#34;&gt;&amp;ldquo;feel&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s get some of the bad out of the way first. Error messages&amp;hellip;omg. To zig&amp;rsquo;s
credit, it does a fantastic job providing the detail you sometimes need.
But let&amp;rsquo;s look at an example I hit all the time. This line of code prints
as you would expect:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;std.log.info(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;All your codebase are belong to us.&amp;#34;&lt;/span&gt;, .{});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If I wanted to print a value, I could say:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;std.log.info(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;All your codebase are belong to us. foo={s}&amp;#34;&lt;/span&gt;, .{foo});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But if I forget to put in the &lt;code&gt;{s}&lt;/code&gt;, like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;std.log.info(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;All your codebase are belong to us. foo=&amp;#34;&lt;/span&gt;, .{foo});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I will get a screenful of the error return trace including all 12 calls that
were in the chain, even if this is only in the main function. You get used
to it, but it leaves you with a certain sense of dread seeing these types
of errors.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s cosmetic, but one thing that&amp;rsquo;s a bit more tangible is something I miss
from a few other languages, most notably Rust. That is, when coding I sometimes
really just want everything to be an expression. Zig has an example of returning
from a block, for instance, that just feels a bit wonky. This example is
in the current documentation for example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-zig&#34; data-lang=&#34;zig&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// use compile-time code to initialize an array
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; fancy_array &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; init&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; initial_value&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;]Point &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;undefined&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; (initial_value) &lt;span style=&#34;color:#f92672&#34;&gt;|*&lt;/span&gt;pt, i&lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pt.&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Point{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .x &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @intCast(&lt;span style=&#34;color:#66d9ef&#34;&gt;i32&lt;/span&gt;, i),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .y &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; @intCast(&lt;span style=&#34;color:#66d9ef&#34;&gt;i32&lt;/span&gt;, i) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;break&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt;init initial_value;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While I&amp;rsquo;m glad this is possible, Rust would handle that by just ending here
with a last line of &lt;code&gt;initial_value&lt;/code&gt; (excluding a semicolon), indicating that
the block&amp;rsquo;s &amp;ldquo;value&amp;rdquo; is initial_value. This feels a bit more natural to me,
though I&amp;rsquo;m sure there are lots of reasons why other parts of zig make this
undesirable or impossible.  I&amp;rsquo;m not a language designer, but I do get to write
words on the Internet. ;-) I will note that shortly after writing the initial
draft of this post there was a short discussion by the zig team on IRC touching
on this in particular as being a bit awkward.&lt;/p&gt;
&lt;p&gt;The focus of the language is currently on building a self-hosted compiler, so
it&amp;rsquo;s not really fair to pick on other things quite yet, but I&amp;rsquo;ll take a quick
shot anyway. The standard library really needs some attention. It is inconsistent,
even with regards to naming standards, and is poorly documented. The idioms
used are also inconsistent, which I believe leads to confusion. I hope that
the core team can get some focus time on it soon.&lt;/p&gt;
&lt;p&gt;Which brings me to one of the bright spots, actually, which is the community.
I&amp;rsquo;ve lurked on IRC, seen the Issue and PR traffic, and seen the twitter
threads.  &lt;a href=&#34;https://andrewkelley.me/&#34;&gt;Andrew&lt;/a&gt; and it seems everyone involved
with the language is passionate and helpful to anyone regardless of their
knowledge of zig. I&amp;rsquo;ve yet to see a hostile attitude from any zig contributor.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Multi-architecture docker builds: notes on ARM</title>
      <link>https://emil.lerch.org/multi-architecture-docker-builds-notes-on-arm/</link>
      <pubDate>Thu, 28 May 2020 16:16:48 -0700</pubDate>
      
      <guid>https://emil.lerch.org/multi-architecture-docker-builds-notes-on-arm/</guid>
      <description>&lt;h1 id=&#34;multi-architecture-docker-builds-notes-on-arm&#34;&gt;Multi-architecture docker builds: notes on ARM&lt;/h1&gt;
&lt;p&gt;There are significant dragons in trying to support a wide variety of ARM chips.
There are also a significant number of dragons in multi-architecture Docker
builds. This post gathers some of my experiences working with multi-architecture
docker builds.&lt;/p&gt;
&lt;p&gt;ARM has emerged as an important processor architecture. Combining the &lt;a href=&#34;https://en.wikipedia.org/wiki/ARM_architecture&#34;&gt;ARM
Architecture&lt;/a&gt;/ISA, for which a
license can be purchased, with custom silicon, several companies have had
a lot of success in the market:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low cost system on a chip (SOC), like &lt;a href=&#34;https://www.raspberrypi.org&#34;&gt;Raspberry Pi&lt;/a&gt; devices&lt;/li&gt;
&lt;li&gt;Android and iPhone devices&lt;/li&gt;
&lt;li&gt;AWS, through custom &lt;a href=&#34;https://aws.amazon.com/ec2/graviton/&#34;&gt;Graviton&lt;/a&gt; chips&lt;/li&gt;
&lt;li&gt;Apple &lt;a href=&#34;https://en.wikipedia.org/wiki/Apple_M1&#34;&gt;M1&lt;/a&gt; Macs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My hope is that another up and coming processor, &lt;a href=&#34;https://en.wikipedia.org/wiki/RISC-V&#34;&gt;RISC-V&lt;/a&gt;,
gets equivalent market penetration. The advantages of this
&lt;a href=&#34;https://en.wikipedia.org/wiki/Instruction_set_architecture&#34;&gt;ISA&lt;/a&gt; basically
comes down to the fact that it is a &lt;a href=&#34;https://en.wikipedia.org/wiki/Reduced_instruction_set_computer&#34;&gt;RISC&lt;/a&gt;
architecture, consistent and &lt;a href=&#34;https://en.wikipedia.org/wiki/Open_source&#34;&gt;open source&lt;/a&gt;.
But ARM is a great step as many companies can (and have) licensed ARM
architectures, and as such multiple competing implementations exist.&lt;/p&gt;
&lt;h2 id=&#34;linux-processor-architectures&#34;&gt;Linux processor architectures:&lt;/h2&gt;
&lt;p&gt;Using &lt;code&gt;uname -m&lt;/code&gt; will provide the CPU architecture on &lt;a href=&#34;https://pubs.opengroup.org/onlinepubs/9699919799/utilities/uname.html&#34;&gt;POSIX systems&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On my &lt;a href=&#34;https://www.raspberrypi.org/pi-zero-w/&#34;&gt;Raspberry Pi Zero&lt;/a&gt;, we see
something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ uname -m
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;armv6l
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can now translate this to:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Arm&lt;/code&gt;: Obviously, this is ARM
&lt;code&gt;v6&lt;/code&gt;: version 6. Generally version 5, 6, 7 and 8 are still supported in 2021.
&lt;code&gt;l&lt;/code&gt;: little &lt;a href=&#34;https://en.wikipedia.org/wiki/Endianness&#34;&gt;endian&lt;/a&gt;, which is fairly common&lt;/p&gt;
&lt;p&gt;In ARM-land, the issue of whether or not the CPU has a hardware floating point
unit is a big deal. Generally processors that you see will be &amp;ldquo;hf&amp;rdquo; in 2020,
but you cannot count on it, especially in low end or embedded devices.&lt;/p&gt;
&lt;p&gt;For ARM 64 bit architectures, this command will simply provide the output
&amp;ldquo;aarch64&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I believe mostly &lt;code&gt;uname -m&lt;/code&gt; for linux would found via the following search
across its source code: &lt;a href=&#34;https://github.com/torvalds/linux/search?p=1&amp;amp;q=UTS_MACHINE&#34;&gt;https://github.com/torvalds/linux/search?p=1&amp;amp;q=UTS_MACHINE&lt;/a&gt;.
This would surface all the definitions for the variable UTS_MACHINE, which is in
turn pulled when running &lt;code&gt;uname -m&lt;/code&gt;. ARM being a little different, ARM 32 bit
is a calculated value that doesn&amp;rsquo;t show up in these results. Take a look at
&lt;a href=&#34;https://github.com/torvalds/linux/blob/95f05058b2bbe3b85c8617b961879e52f692caa5/arch/arm/Makefile&#34;&gt;https://github.com/torvalds/linux/blob/95f05058b2bbe3b85c8617b961879e52f692caa5/arch/arm/Makefile&lt;/a&gt;
for a reasonable picture of the 32 bit landscape for ARM.&lt;/p&gt;
&lt;h2 id=&#34;debian-architecture-names&#34;&gt;Debian architecture names&lt;/h2&gt;
&lt;p&gt;Debian (and derivatives, like Ubuntu), being such a huge portion of the linux
landscape, we need to consider their architectures. These are defined via their
&lt;a href=&#34;https://www.debian.org/ports/#portlist-released&#34;&gt;ports&lt;/a&gt;. Most relevant ARM
ports are officially supported. Here is the list, descriptions taken directly
from the page:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;armel: The oldest of the current Debian ARM ports supports little-endian
ARM CPUs compatible with the v5te instruction set.&lt;/li&gt;
&lt;li&gt;armhf: A lot of modern 32-bit ARM boards and devices ship with a
floating-point unit (FPU), but the Debian armel port doesn&amp;rsquo;t take
much advantage of it. The armhf port was started to improve this
situation and also take advantage of other features of newer ARM CPUs.
The Debian armhf port requires at least an ARMv7 CPU with Thumb-2
and VFPv3-D16 floating point support.&lt;/li&gt;
&lt;li&gt;arm64: Version 8 of the ARM architecture included AArch64, a new 64-bit
instruction set. Since Debian 8.0, the arm64 port has been included
in Debian to support this new instruction set on processors such as
the Applied Micro X-Gene, AMD Seattle and Cavium ThunderX.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In Debian-land, armhf= armv7 32 bit&lt;/p&gt;
&lt;p&gt;Unofficially, there is also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;arm: This port runs on a variety of embedded hardware, like routers
or NAS devices. The arm port was first released with Debian 2.2,
and was supported up to and including Debian 5.0, where it was replaced
with armel.&lt;/li&gt;
&lt;li&gt;armeb: ARM, software Emulated floating point, Big endian. I have seen
references to this, but not seen any in the wild.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;32-bit-architectures&#34;&gt;32 bit architectures&lt;/h2&gt;
&lt;p&gt;These are important when building for compatibility across a wide spectrum of
devices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;arm5: I believe this is mostly obsolete&lt;/li&gt;
&lt;li&gt;arm6: Used on Raspberry Pi Zero/Zero W and some low end and/or old android phones
Few docker containers support this architecture&lt;/li&gt;
&lt;li&gt;arm7: Considered &amp;ldquo;hard float&amp;rdquo; by most, and seems to be the most popular in the wild&lt;/li&gt;
&lt;li&gt;arm8: This can work for 32 or 64 bit, but generally 64 bit is used for v8-specific builds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In docker, these are all considered &amp;ldquo;arm&amp;rdquo; architecture. Docker has introduced
a concept called &amp;ldquo;variant&amp;rdquo;. In docker hub, you will see arm7, for instance,
noted as &amp;ldquo;linux/arm/armv7&amp;rdquo; In docker hub, you will see arm7, for instance,
noted as &amp;ldquo;linux/arm/v7&amp;rdquo;. This last portion is the variant.&lt;/p&gt;
&lt;h2 id=&#34;64-bit-architectures&#34;&gt;64 bit architectures&lt;/h2&gt;
&lt;p&gt;Arm64/Aarch64 seems a lot more consistent. Since 2011, there has been only
arm8.x, and software compiled for arm8/arm64/aarch64 only seems to need one
variant. Arm64/aarch64 is used &lt;a href=&#34;https://stackoverflow.com/questions/31851611/differences-between-arm64-and-aarch64&#34;&gt;depending on who you are&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Raspbian and Debian &lt;a href=&#34;https://raspberrypi.stackexchange.com/questions/87392/pi1-armv6-how-to-disable-armhf-packages/87403#87403&#34;&gt;disagree&lt;/a&gt;
on what armhf mean. Basically Raspbian says &amp;lsquo;armhf&amp;rsquo; includes any arm processor
with hard float. Debian uses armhf to mean arm7+ 32 bit architectures.
However, arm8+ are 64 bit, so armhf in Debian realistically means just&lt;/p&gt;
&lt;p&gt;&lt;code&gt;readelf -A /proc/self/exe | grep Tag_ABI_VFP_args &amp;amp;&amp;amp; echo &#39;Hard floating point&#39;&lt;/code&gt;
If this command outputs a line &amp;ldquo;Hard floating point&amp;rdquo;, Raspian will
use armhf packages. However, debian requires arm7 to use armhf packages.&lt;/p&gt;
&lt;p&gt;Docker is a mess because arm variant detection &lt;a href=&#34;https://github.com/moby/moby/issues/37647&#34;&gt;basically does not exist&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;building-an-image&#34;&gt;Building an image&lt;/h2&gt;
&lt;p&gt;Sticking to the Docker ecosystem, I&amp;rsquo;ve had some success with multi-architecture
docker builds generally following the advice on this page:
&lt;a href=&#34;https://medium.com/@artur.klauser/building-multi-architecture-docker-images-with-buildx-27d80f7e2408&#34;&gt;https://medium.com/@artur.klauser/building-multi-architecture-docker-images-with-buildx-27d80f7e2408&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Gotchas I&amp;rsquo;ve run into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Everything with multi-architecture images is experimental. You need a recent
operating system, a recent docker, a recent kernel, and you need to enable
experimental features. There is even a table on the post above that walks
through this. If you&amp;rsquo;re host environment doesn&amp;rsquo;t match a working configuration
from the &lt;a href=&#34;https://miro.medium.com/max/700/1*7L3hU-9LIFY9LU-rrTmfXg.png&#34;&gt;table in the blog post&lt;/a&gt;,
then go back and fix that first, or you will be frustrated. Follow this
carefully - even on a pretty modern system (debian buster new install)
I needed to run their &amp;ldquo;fix-it script&amp;rdquo;, and even that script needed some tweaks.&lt;/li&gt;
&lt;li&gt;Once you have qemu-static and all the configuration, it&amp;rsquo;s pretty seamless to
run programs compiled for another architecture. It can get super confusing as
everything &amp;ldquo;works&amp;rdquo;, but then on deploy, it doesn&amp;rsquo;t, because qemu stepped in
during your testing and you didn&amp;rsquo;t even realize it. &lt;code&gt;file&lt;/code&gt; is your friend,
docker pull is &lt;strong&gt;not&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker pull --architecture&lt;/code&gt; works as you expect, but if you, for instance,
do &lt;code&gt;docker pull --architecture linux/aarch64 myimage:latest&lt;/code&gt;, then later
do &lt;code&gt;docker pull myimage:latest&lt;/code&gt; expecting amd64 (for Intel or AMD CPUs),
you will &lt;em&gt;not get what you expect&lt;/em&gt;. Docker will happily look at its cache
and say &amp;ldquo;yeah, I&amp;rsquo;ve already got that image&amp;rdquo; and serve up the wrong CPU
architecture. And you won&amp;rsquo;t notice until much later when everything is
broken and you&amp;rsquo;re stuck in the office while your team is out drinking
(best case) or hovering over your shoulder (worst).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This post is also useful: &lt;a href=&#34;https://www.docker.com/blog/multi-arch-build-and-images-the-simple-way/&#34;&gt;https://www.docker.com/blog/multi-arch-build-and-images-the-simple-way/&lt;/a&gt;
However, it also focuses on buildx, which is a docker cli plugin (another
piece of software to install, and only enabled with &lt;code&gt;DOCKER_CLI_EXPERIMENTAL=enabled&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Because I like &lt;a href=&#34;https://podman.io&#34;&gt;podman&lt;/a&gt; due to its lack of daemon and
rootless containers, I&amp;rsquo;ve taken a shot at making this work there as well.
The good news is that the manifest commands are similar (though not identical).
The bad news is that pulling different architecture images just seems like
a broken situation. I&amp;rsquo;m sure it can be fixed, but my current &lt;a href=&#34;https://www.drone.io&#34;&gt;drone&lt;/a&gt;
install is on docker proper, so I haven&amp;rsquo;t had a chance to investigate yet.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>PKCS 11, OpenPGP, Yubikeys/Solokeys, and Windows AMIs</title>
      <link>https://emil.lerch.org/pkcs-11-openpgp-yubikeys/solokeys-and-windows-amis/</link>
      <pubDate>Mon, 02 Dec 2019 16:16:48 -0700</pubDate>
      
      <guid>https://emil.lerch.org/pkcs-11-openpgp-yubikeys/solokeys-and-windows-amis/</guid>
      <description>&lt;h1 id=&#34;using-single-key-for-both-pkcs11-piv-app-on-yubikey-and-openpgpgnupg&#34;&gt;Using single key for both PKCS#11 (PIV app on Yubikey) and OpenPGP/GnuPG&lt;/h1&gt;
&lt;p&gt;I was looking at creating a Windows instance on AWS EC2 over the weekend, and
I started thinking about the administrator password. In AWS on Linux and likely
other Unix-like OS&amp;rsquo;s on EC2, you can provide a public SSH key and through the
magic of &lt;a href=&#34;https://cloud-init.io/&#34;&gt;cloud-init&lt;/a&gt;, the public key is placed in the
.ssh directory of the user, &lt;a href=&#34;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connection-prereqs.html#connection-prereqs-get-info-about-instance&#34;&gt;which varies based on the AMI chosen&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Windows on EC2, however, follows a different process. Here, the SSH public key
is used a generic public key. When the Administrator process is &lt;a href=&#34;https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/ec2launch.html#ec2launch-tasks&#34;&gt;set by EC2Launch&lt;/a&gt;,
That password is encrypted using the public key provided, then you can decrypt
the data with your private key. This can be done through the console, or with
the command line &lt;code&gt;aws ec2 get-password-data&lt;/code&gt;. The console, however, requires
that you upload your private key, while the CLI version only encourages it.&lt;/p&gt;
&lt;p&gt;In my setup, pretty much the only key pair I use is generated via
&lt;a href=&#34;https://gnupg.org/&#34;&gt;gpg&lt;/a&gt; on an airgapped machine running &lt;a href=&#34;https://tails.boum.org/&#34;&gt;Tails&lt;/a&gt;.
The private key is copied to two USB sticks (primary and backup), then imported
onto a Yubikey NEO, which is still somewhat open source (Sorry, Yubico, but I
respectfully &lt;a href=&#34;https://www.yubico.com/2016/05/secure-hardware-vs-open-source/&#34;&gt;disagree&lt;/a&gt;).
I&amp;rsquo;m pretty happy with this arrangement, but it means that I can&amp;rsquo;t provide my
private ssh key to an API or a console (nor would I want to).&lt;/p&gt;
&lt;p&gt;I started thinking about how retrieval of this data could work in an environment
with a Yubikey or an HSM. GPG is strict about the data it decrypts - it must be
in the PGP format, which is &lt;strong&gt;not&lt;/strong&gt; how &lt;a href=&#34;https://docs.aws.amazon.com/cli/latest/reference/ec2/get-password-data.html#examples&#34;&gt;get-password-data&lt;/a&gt;
provides it&amp;rsquo;s data. Generally, the idea here is that if the private key is not
provided, the data will be returned encrypted and base 64 encoded. The idea
then is to base 64 decode the data, write a file out, and issue a command like
&lt;code&gt;openssl rsautil -decrypt -inkey mykey &amp;lt;mypassword.bin&lt;/code&gt; to
decrypt it. That command may not work - it&amp;rsquo;s just an example.&lt;/p&gt;
&lt;p&gt;Without direct access to the private key, using a command such as the above is
not possible. Nor is the use of GPG. The solution, then, is to use the Yubikey&amp;rsquo;s
support for &lt;a href=&#34;https://en.wikipedia.org/wiki/PKCS_11&#34;&gt;PKCS#11&lt;/a&gt;. This is provided
by the &lt;a href=&#34;https://developers.yubico.com/PIV/&#34;&gt;PIV&lt;/a&gt; applet on the key. This applet,
however, has completely separate storage from the OpenPGP applet, so the keys,
PINs, etc are all managed separately, even if the concepts are shared between
OpenPGP (accessed by GPG) and PKCS#11 (accessed by PKCS client tools like
pkcs11_tool).&lt;/p&gt;
&lt;p&gt;In an ideal world, then, what I&amp;rsquo;d like to have is a single key, loaded into
both OpenPGP and PIV applets, used for SSH access (via gpg-agent) and Windows
passwords (accessed via pkcs11_tool). If it were just a question of SSH, we
could remove GPG entirely, but I also use the GPG key for
&lt;a href=&#34;https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work&#34;&gt;commit signing&lt;/a&gt;,
so I&amp;rsquo;m in a GPG world anyway. The GPG key has separate subkeys for signing,
authentication and encryption, so that&amp;rsquo;s good separation of duties. It&amp;rsquo;s worth
considering that I&amp;rsquo;m breaking this separation with the procedure below and it
might be best to have two EC2 KeyPairs, one for Windows and one for SSH, but
the differences in the procedure amount to the selection of a different subkey,
and the rest is just discipline. GPG does the enforcement, and we&amp;rsquo;re explicitly
taking out that enforcement.&lt;/p&gt;
&lt;p&gt;Generally, what I was looking to do is the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Export the gpg private and public key&lt;/li&gt;
&lt;li&gt;Convert the gpg private key to PEM format&lt;/li&gt;
&lt;li&gt;Load the private key into the PIV applet on the Yubikey&lt;/li&gt;
&lt;li&gt;Use PKCS#11 interface to decrypt the password data&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;step-0-getting-started&#34;&gt;Step 0: Getting started&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re like me and set up a GPG key with an expiration that needs to
periodically be extended, this process will need to repeat. However, this
particular step will only need to be done once. For that reason along with
the fact that &lt;a href=&#34;https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html&#34;&gt;numbering should start at zero&lt;/a&gt;,
this one time only process will be step 0.&lt;/p&gt;
&lt;p&gt;First things last: I had significant confusion when working with Yubikey and
PKCS#11. Nothing I did worked. I found that GPG agent (probably) was blocking
all PKCS#11 access. Simply removing and reinserting the key was able to unblock
me.&lt;/p&gt;
&lt;p&gt;So we basically have a default key. Even if it was configured previously, none
of that has anything to do with PIV, so forget that. You&amp;rsquo;ll need the
&lt;a href=&#34;https://developers.yubico.com/yubico-piv-tool/&#34;&gt;yubico-piv-tool&lt;/a&gt; to configure
the key. Even though it speaks PKCS#11, the configuration is unique to Yubikey.
&lt;a href=&#34;https://gist.github.com/faun/20b292ed2ccc36d7e4733d7329148dca&#34;&gt;This gist&lt;/a&gt;
does a good job describing the steps, however, it&amp;rsquo;s focused on on-key generated
keys, which is a problem if the key is lost. For now, we&amp;rsquo;ll just get the
Yubikey initialized, changing the three management pins to something other than
their default values:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Set the managment key: must be exactly 48 characters&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  yubico-piv-tool -a set-mgm-key
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Set the device PIN (user pin - default 123456)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Since this default is well known, it&amp;#39;s ok to pass it on the command line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  yubico-piv-tool -a change-pin -P123456
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Please set the device PUK (Admin pin - default 12345678)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Since this default is well known, it&amp;#39;s ok to pass it on the command line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  yubico-piv-tool -a change-puk -P12345678
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;step-1-export-the-gpg-private-and-public-key&#34;&gt;Step 1: Export the gpg private and public key&lt;/h2&gt;
&lt;p&gt;This step is easy, but has it&amp;rsquo;s nuances. Private keys should be handled with
extreme care. For me, that means the key is stored on a physically secured
USB key (with a secondary backup key), and all key operations are on an
airgapped computer running &lt;a href=&#34;https://tails.boum.org/&#34;&gt;Tails&lt;/a&gt;. Since these
files are only used for this process, they can be managed from on the tails
filesystem, which will be wiped on shutdown.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Replace mykey with the uid of your key. This is often your email address.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# It can be seen in the command gpg --list-keys on one of the uid lines&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# If you have multiple uid lines, any one of them will work&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  gpg --export-secret-key mykey &amp;gt; mykey.gpg &lt;span style=&#34;color:#75715e&#34;&gt;# OpenPGP format secret key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  gpg --export-ssh-key mykey &amp;gt; mykey.pub    &lt;span style=&#34;color:#75715e&#34;&gt;# OpenSSH format public key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this stage, we&amp;rsquo;ll have the public key in OpenSSH format documented in
&lt;a href=&#34;https://tools.ietf.org/html/rfc4253#section-6.6&#34;&gt;RFC 4253&lt;/a&gt;. This will work
for our needs. However, the GPG private key is in the &lt;a href=&#34;https://tools.ietf.org/html/rfc4880#section-5.5&#34;&gt;OpenPGP Message Format
for key material&lt;/a&gt; and as such
needs a conversion to &lt;a href=&#34;https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail&#34;&gt;PEM format&lt;/a&gt;
to be useful.&lt;/p&gt;
&lt;h2 id=&#34;step-2-convert-the-gpg-private-key&#34;&gt;Step 2: Convert the GPG private key&lt;/h2&gt;
&lt;p&gt;At this stage, we need another tool. The &lt;a href=&#34;https://web.monkeysphere.info/&#34;&gt;MonkeySphere project&lt;/a&gt;
is focused on expanding GPG to other uses, and the packages they provide
allow for conversion of keys among other things. So, we can just use the
tool they provide to do the conversion. However, there is one caveat: the tool
cannot convert password-protected keys. If you&amp;rsquo;ve been managing keys securely
you certainly have a password protected key, so we need to remove this. It is for
this reason you really want to do this on an ephemeral file system. We&amp;rsquo;ll use
the &lt;code&gt;gpg --homedir&lt;/code&gt; option to override the normal home directory. Choose
something that will self-destruct, remove the passwords from the key, do another
export, and the conversion can work. This looks like the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Replacements:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# * ephemeralDirectory: your ephemeral directory name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# * mykey.gpg: your exported file from Step 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# * mykey: key uid, which will be output during the gpg import command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#          when it says key &amp;lt;keyid&amp;gt; public key &amp;#34;your uid&amp;#34; imported&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# * DD53AC86: This is the key id from the authentication subkey of your&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#             key. Get this id from the command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#             gpg --list-keys --with-subkey-fingerprint&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#             Look for the line that says something like&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#             sub rsa2048 &amp;lt;date&amp;gt; [A]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#             A is for Authentication, and is what is used for SSH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;#             The key id is the **last** 8 characters of the next line&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  gpgtemphome&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;myephemeralDirectory
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  mkdir $gpgtemphome
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  chmod &lt;span style=&#34;color:#ae81ff&#34;&gt;700&lt;/span&gt; $gpgtemphome
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  cd $gpgtemphome
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  gpg --homedir $gpgtemphome --import mykey.gpg &lt;span style=&#34;color:#75715e&#34;&gt;# you will be prompted for the password here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  gpg --homedir $gpgtemphome --passwd mykey     &lt;span style=&#34;color:#75715e&#34;&gt;# At this point, you will be prompted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                &lt;span style=&#34;color:#75715e&#34;&gt;# for new passwords, leave them&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                &lt;span style=&#34;color:#75715e&#34;&gt;# blank, then confirm that you want&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                &lt;span style=&#34;color:#75715e&#34;&gt;# a blank password. This will likely&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                &lt;span style=&#34;color:#75715e&#34;&gt;# happen 3 times, once for each&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                &lt;span style=&#34;color:#75715e&#34;&gt;# of the subkeys of the imported key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  gpg --homedir $gpgtemphome --export-secret-key mykey &amp;gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mykey.gpg.nopass&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  openpgp2ssh DD53AC86 &amp;lt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mykey.gpg.nopass&amp;#34;&lt;/span&gt; &amp;gt;mykey.pem &lt;span style=&#34;color:#75715e&#34;&gt;# pem format secret key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you were successful, you&amp;rsquo;ll have a PEM format secret key file in
&lt;code&gt;$gpgtemphome/mykey.pem&lt;/code&gt; that can be now be loaded onto the Yubikey. The
hard part is now done.&lt;/p&gt;
&lt;h2 id=&#34;step-3-load-the-private-key-into-the-piv-applet-on-the-yubikey&#34;&gt;Step 3: Load the private key into the PIV applet on the Yubikey&lt;/h2&gt;
&lt;p&gt;All PKCS#11 operations on the Yubikey work in &lt;a href=&#34;https://www.yubico.com/smart-card/&#34;&gt;slot 9a&lt;/a&gt;.
So, we need to load the key into slot 9a of the PIV applet. It&amp;rsquo;s almost, but not
quite, that simple. For this to operate correctly, we need a self-signed cert
rather than just the key. The following commands will import the key, create
the cert, and load it. Most of these commands will also require the management
key to operate successfully. Anytime you see &lt;code&gt;-k&lt;/code&gt; below, you&amp;rsquo;ll be prompted.&lt;/p&gt;
&lt;p&gt;Note that pin-policy and touch-policy parameters only apply to Yubikey 4. I&amp;rsquo;m
working with the (sort of) open source Yubikey Neo. You may want to adjust
these to your taste. The &lt;code&gt;-v&lt;/code&gt; turns on verbose mode, which I found helpful
when the terminal wasn&amp;rsquo;t pasting the management key.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# This will prompt for the management key, without which you cannot import the key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Replace mykey.pem as appropriate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  yubico-piv-tool -s 9a -a import-key -i mykey.pem -k --pin-policy&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;once --touch-policy&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;always -v
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Generate the the certificate to load. Replace mykey.pub with the file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# exported in Step 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ssh-keygen -e -f mykey.pub -m PKCS8 &amp;gt; mykey.pub.pkcs8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Replace mykey.pub.pkcs8 name as appropriate from the command above. This creates a &amp;#34;mykey-cert.pem&amp;#34; file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Yubikey piv tool requires the file to be named with a pem suffix, so don&amp;#39;t get fancy with the file sufix above&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  yubico-piv-tool -a verify -a selfsign-certificate -s 9a -S &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/CN=SSH&amp;#34;&lt;/span&gt; -i mykey.pub.pkcs8 -o mykey-cert.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Finally, we can import the certificate we generated into slot 9a.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# This will ask for the management key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# Yubikey piv tool requires the file to be named with a pem suffix, so don&amp;#39;t get fancy with the file sufix above&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  yubico-piv-tool -a verify -a import-certificate -s 9a -i mykey-cert.pem -k
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point, you should be able to verify the key is properly loaded on the
Yubikey. Your gpg exported ssh public key (in my example, &amp;ldquo;mykey.pub&amp;rdquo;) should
match what comes off the Yubikey via PKCS#11. The ssh key from gpg will have a
comment - the command below uses the Unix command cut to strip that out.
As usual, replace the mykey.pub name with the name you used:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;cut -f1-2 -d&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; &amp;lt;mykey.pub&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;ssh-keygen -D /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so -e&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;File and smartcard match&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With any luck, you should see the output &lt;code&gt;File and smartcard match&lt;/code&gt;. If you
don&amp;rsquo;t, something went wrong. You can also test encrypt/decrypt operations with
something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;it worked&amp;#39;&lt;/span&gt; | openssl pkeyutl -encrypt -inkey mykey.pem -pubin &amp;gt; encrypted.bin
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pkcs11-tool --decrypt -v -l --input-file encrypted.bin  -m RSA-PKCS &lt;span style=&#34;color:#75715e&#34;&gt;# Prints &amp;#39;it worked&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That should execute without errors and print &amp;lsquo;it worked&amp;rsquo;. From the first test
we know that the ssh key is properly loaded in slot 9a, and the second proves
that decryption from the Yubikey using PKCS#11 works properly.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re done, and you can now delete the files we&amp;rsquo;ve been generating along the
way. In my example, that&amp;rsquo;s:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mykey.pub&lt;/li&gt;
&lt;li&gt;mykey-cert.pem&lt;/li&gt;
&lt;li&gt;mykey.pub.pkcs8&lt;/li&gt;
&lt;li&gt;mykey.pem&lt;/li&gt;
&lt;li&gt;mykey.gpg.nopass&lt;/li&gt;
&lt;li&gt;mykey.gpg&lt;/li&gt;
&lt;li&gt;encrypted.bin (if you ran the last test)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Assuming you&amp;rsquo;re doing this on an airgapped computer, go ahead and shut down and
remove the Yubikey.&lt;/p&gt;
&lt;h2 id=&#34;step-4-use-pkcs11-interface-to-decrypt-the-password-data&#34;&gt;Step 4: Use PKCS#11 interface to decrypt the password data&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re loaded up. If you&amp;rsquo;re doing all this for the same reasons as I, go ahead
and &lt;a href=&#34;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/LaunchingAndUsingInstances.html&#34;&gt;launch an EC2 Windows instance&lt;/a&gt;.
You&amp;rsquo;ll need to establish an &lt;a href=&#34;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#how-to-generate-your-own-key-and-import-it-to-aws&#34;&gt;EC2 keypair&lt;/a&gt;
if you don&amp;rsquo;t already have one, and specify that key pair when you launch the
instance.&lt;/p&gt;
&lt;p&gt;Once the instance has launched, we&amp;rsquo;ll use the CLI command &lt;a href=&#34;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html#how-to-generate-your-own-key-and-import-it-to-aws&#34;&gt;get-password-data&lt;/a&gt;
to retrieve our data. It comes in a JSON format, base64 encoded, so the
command will look like the below. Note we need a file as the pkcs11_tool does
not read from stdin (as it needs to prompt for the user PIN). Also,
the password is not established immediately, so you may need to wait a bit
for the data to appear.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;aws ec2 get-password-data --instance-id &amp;lt;blah&amp;gt; --query PasswordData --output text| base64 -d &amp;gt; encrypted-adminpass.bin
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pkcs11-tool --decrypt -v -l --input-file encrypted-adminpass.bin -m RSA-PKCS
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;connecting-to-the-windows-instance&#34;&gt;Connecting to the Windows instance.&lt;/h2&gt;
&lt;p&gt;There may be an issue connecting, as newer Windows AMIs enable NLA by default.
&lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/index.html&#34;&gt;Simple Systems Manager&lt;/a&gt;
has an agent installed by default, so in these cases you can attach an appropriate
IAM role and issue the following command, replacing the instance id below
with yours:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;aws ssm start-automation-execution --document-name &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;AWSSupport-ManageRDPSettings&amp;#34;&lt;/span&gt; --parameters &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;InstanceId=i-03033520993ddf97f,NLASettingAction=Disable&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;but-wait-what-does-all-this-have-to-do-with-solokeys&#34;&gt;But wait, what does all this have to do with Solokeys?!&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&#34;https://solokeys.com/&#34;&gt;Solokey&lt;/a&gt; is a truly open source security key. For
now, it handles only Fido2 and U2F. However, they&amp;rsquo;re working on &lt;a href=&#34;https://github.com/solokeys/openpgp&#34;&gt;OpenPGP&lt;/a&gt;
support. PKCS#11 would be over and above, but it&amp;rsquo;s possible with something like
&lt;a href=&#34;www.scute.org&#34;&gt;scute&lt;/a&gt; that PKCS#11 support could be provided via OpenPGP.
In this process, however, I&amp;rsquo;ve heard rumblings that scute is somewhat unstable.
It&amp;rsquo;s also unclear whether the authentication subkey would be allowed to work
for decryption in this particular use case.&lt;/p&gt;
&lt;h2 id=&#34;more-information&#34;&gt;More information&lt;/h2&gt;
&lt;p&gt;The following posts/links helped me immensely on this journey:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gist.github.com/faun/20b292ed2ccc36d7e4733d7329148dca&#34;&gt;https://gist.github.com/faun/20b292ed2ccc36d7e4733d7329148dca&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/google-cloud/google-cloud-ssh-with-os-login-with-yubikey-opensc-pkcs11-and-trusted-platform-module-tpm-based-86fa22a30f8d&#34;&gt;https://medium.com/google-cloud/google-cloud-ssh-with-os-login-with-yubikey-opensc-pkcs11-and-trusted-platform-module-tpm-based-86fa22a30f8d&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://gnupg.10057.n7.nabble.com/Private-key-export-for-SSH-td49236.html&#34;&gt;http://gnupg.10057.n7.nabble.com/Private-key-export-for-SSH-td49236.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.rchapman.org/posts/Import_an_existing_ssh_key_into_Yubikey_NEO_applet/&#34;&gt;https://blog.rchapman.org/posts/Import_an_existing_ssh_key_into_Yubikey_NEO_applet/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;updates&#34;&gt;Updates&lt;/h2&gt;
&lt;p&gt;Just after publishing this post, &lt;a href=&#34;https://www.zdnet.com/article/two-malicious-python-libraries-removed-from-pypi/&#34;&gt;this link&lt;/a&gt;
came across an email list I subscribe to. In a nutshell, two Python libraries
were caught exfiltrating private SSH and GPG keys. Storing your private keys
on your Yubikey completely render this attack moot, as no private keys are
available in any way.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>COULD_NOT_CREATE_SYNC_ACCOUNT Amazon Prime Video, and why root access is important</title>
      <link>https://emil.lerch.org/could_not_create_sync_account-amazon-prime-video-and-why-root-access-is-important/</link>
      <pubDate>Wed, 02 Oct 2019 16:16:48 -0700</pubDate>
      
      <guid>https://emil.lerch.org/could_not_create_sync_account-amazon-prime-video-and-why-root-access-is-important/</guid>
      <description>&lt;p&gt;Early this year, an automatic upgrade to my Amazon Prime Video application
was installed. I was happily enjoying Mission Impossible, Season 1, downloaded
to the device for use on planes, but the next time I opened the application,
I was greeted with &amp;ldquo;COULD_NOT_CREATE_SYNC_ACCOUNT&amp;rdquo;, and the Prime Video app
refused to load. Well, so be it&amp;hellip;I moved on to other, likely more productive,
usage of my airplane time. However, this error message bugged me, and a 50-ish
minute show is pretty good for taxi+takeoff+climbout to 10k ft (also,
10k feet descend to final approach and landing). So, it stayed in the back of
my mind.&lt;/p&gt;
&lt;p&gt;Fast forward a couple months later, maybe in the summer, and I contacted
Amazon Prime Video support. Amazon support is appropriately customer-obsessed,
and the engineer was relatively helpful, but clearly followed a script that
I&amp;rsquo;ve played through before. Of course, I got to the point of &amp;ldquo;reset your
Android device to factory settings&amp;rdquo;, at which point I pulled my &amp;ldquo;no way&amp;rdquo; card.
Support did fall down somewhat at this point, where I was promised a follow-up
to my issue, but this did not occur.&lt;/p&gt;
&lt;p&gt;A couple months later still, this problem still bothered me. For reasons, I
actually had access to the source code for the application (actually, library)
that produced this error message, but ultimately this wasn&amp;rsquo;t important, other
than to point out the fact that I spent some crazy side time working out how
to build the application and all dependent libraries. It was a great learning
experience, but ultimately it wasn&amp;rsquo;t the easy path here.&lt;/p&gt;
&lt;p&gt;I started to consider what the error message was trying to tell ~me~ a developer,
and it dawned on me that they were talking about the Android accounts feature.
I looked at my phone, another Android device, and noticed that indeed, there
was an &amp;ldquo;Amazon Video Sync&amp;rdquo; account listed in the &amp;ldquo;Accounts&amp;rdquo; section of the
settings. I had never created this account manually, so the upgrade clearly
tried to install this account programmatically and had failed. Ok, now &lt;strong&gt;that&lt;/strong&gt;
I can work with&amp;hellip;&lt;/p&gt;
&lt;p&gt;The account had no settings, but in my further research I found that this is
super-useful from Prime Video&amp;rsquo;s perspective. Android handles all the ugly work
of connectivity and retries and exponential fallback with jitter, battery
saving, etc., and simply calls into the class when everything is good to go.
Then Amazon Prime can sync. It&amp;rsquo;s totally cool here to download videos and
whatnot based on a sync provider. Android Prime Video is just trying to install
the sync provider, and cannot. It needs this, ostensibly to download videos,
but due to the failure, it cannot proceed and fails. All reasonable, my only
question being - should a missing sync provider really fail online viewing? It
seems this should be limited to download for offline videos.&lt;/p&gt;
&lt;p&gt;ok, so the application can&amp;rsquo;t install the sync provider. Let&amp;rsquo;s dive in for why.
So, I first tried to install manually. This was interesting. The account
settings in Android simply hung when I tried to add the account manually.
Well, this is interesting&amp;hellip;&lt;/p&gt;
&lt;h2 id=&#34;root-access-required&#34;&gt;Root access required&lt;/h2&gt;
&lt;p&gt;A few &lt;a href=&#34;https://duckduckgo.com&#34;&gt;DuckDuckGo&lt;/a&gt; searches later, and I find that
deep in the depths of Android, two files control the sync providers shown
on the screen (and presumably in the APIs of Android). These are, at least on
my machine, though I think it varies by Android version:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;/data/system_ce/0/accounts_ce.db&lt;/li&gt;
&lt;li&gt;/data/system/sync/accounts.xml&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So&amp;hellip;the two files worked together. Using &lt;a href=&#34;https://termux.com/&#34;&gt;termux&lt;/a&gt; with
the &lt;a href=&#34;https://sqlite.org/index.html&#34;&gt;sqlite3&lt;/a&gt;
package installed, I opened the database. This stuff is a good way to mess up
your system, so I&amp;rsquo;m not providing further instructions on how this is done.
A quick &lt;code&gt;select * from accounts&lt;/code&gt; yielded 35 accounts, 34 of which were
affiliated with Amazon Prime Video, the &amp;ldquo;name&amp;rdquo; field varied between
&amp;lsquo;Amazon Video Sync &lt;em&gt;n&lt;/em&gt;&amp;rsquo; and &amp;lsquo;hollywood-sync-account_&lt;em&gt;n&lt;/em&gt;&amp;rsquo;, with the latter being
more recent, but also interchanged with a &amp;ldquo;AmazonSyncAccount-&lt;!-- raw HTML omitted --&gt;_&lt;em&gt;n&lt;/em&gt;&amp;rsquo;. This
was clearly a result of my multiple attempts to fix the problem. I created
a backup of the database, ran a &lt;code&gt;delete from accounts where _id &amp;gt; 1&lt;/code&gt; (your
id will &lt;strong&gt;obviously&lt;/strong&gt; vary), and the table looked good. I double checked all
the other tables in that database, but nothing else looked out of line.&lt;/p&gt;
&lt;p&gt;A reboot later and a running of Amazon Prime Video and&amp;hellip;no love. Something so
obviously wrong had been fixed, but I still had the same error. I checked the
database, and sure enough, I had a new entry, but things were still off. So,
I deleted again, then checked the accounts.xml file. The last entry in the
XML was an account named &amp;ldquo;Amazon Video Sync&amp;rdquo;, so I deleted that file in
&lt;strong&gt;addition&lt;/strong&gt; to the relevant accounts table in accounts_ce.db. I rebooted,
and tried Amazon Prime Video. Voila, everything worked.&lt;/p&gt;
&lt;h2 id=&#34;morals-of-the-story&#34;&gt;Morals of the story&lt;/h2&gt;
&lt;p&gt;There are several morals that I&amp;rsquo;ll explicitly mention here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Something in the fall of 2018 or spring of 2019 changed in Amazon Prime
Video Android app changed (probably the name of the sync provider) that
threw things out of whack in the Android sync provider configuration that
neither the UI nor the APIs were ready for.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Factory Reset&amp;rdquo; is a really poor answer to an issue like &amp;ldquo;I can&amp;rsquo;t open
my app anymore&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Root access is important. I understand the customer service anti-pattern
here, but honestly, they&amp;rsquo;re our devices. Provide a &amp;ldquo;this will void your
warranty&amp;rdquo; or whatever, but fixing this issue required either factory
reset or root access to fix. It&amp;rsquo;s much better to let technically savvy
users fix their problems. &lt;a href=&#34;https://www.lineageos.org/&#34;&gt;LineageOS&lt;/a&gt; gets this right.&lt;/li&gt;
&lt;li&gt;The Unix philosopy rules. Text files, or even sql lite databases to control
the system makes it easy to safe-heal. Don&amp;rsquo;t hide everything behind APIs
on a local system.&lt;/li&gt;
&lt;li&gt;Clear error messages allow self-maintenance. Even to me, an experienced dev,
albeit without a lot of Android app experience, did not recognize this error
for a while. In retrospect I feel I should have figured this out earlier,
but consider the use case. I have multiple &amp;ldquo;downloaded video watching&amp;rdquo;
options between &lt;a href=&#34;https://www.netflix.com/&#34;&gt;Netflix&lt;/a&gt;,
&lt;a href=&#34;https://www.plex.tv/&#34;&gt;Plex&lt;/a&gt;, and &lt;a href=&#34;https://play.google.com/store/movies&#34;&gt;Google Play&lt;/a&gt;,
so this was bothersome but not a high priority for me. I fixed this on
my birthday as I had blocked a good portion of my birthday to focus on
low priority nagging technical issues&amp;hellip;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Year of Linux on the Desktop</title>
      <link>https://emil.lerch.org/year-of-linux-on-the-desktop/</link>
      <pubDate>Sat, 03 Aug 2019 09:12:13 -0700</pubDate>
      
      <guid>https://emil.lerch.org/year-of-linux-on-the-desktop/</guid>
      <description>&lt;p&gt;I have used Linux under one form or another since &lt;a href=&#34;https://en.wikipedia.org/wiki/Slackware&#34;&gt;Slackware&lt;/a&gt;
around 1994. Not sure when exactly, but the kernel was definitely pre-1.0 and
&lt;a href=&#34;https://en.wikipedia.org/wiki/Executable_and_Linkable_Format&#34;&gt;ELF&lt;/a&gt; was not
yet a thing. Since as long as I can remember, people have said &amp;ldquo;xxxx is
the year of Linux on the deskop&amp;rdquo;. I haven&amp;rsquo;t found any good citations for this,
but there is a &lt;a href=&#34;https://www.reddit.com/r/linux/comments/3038d4/when_was_the_first_year_of_the_linux_desktop/&#34;&gt;reddit thread on the subject&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Linux is all around us, most predominately in servers, but also in IoT devices,
our infomatics systems in our cars, our watches, our streaming devices
(Roku, Chromecast, Fire sticks and the like), Amazon Echos, light switches,
printers, etc. Every Android device currently runs a Linux kernel, though much
of the Apple ecosystem does not.&lt;/p&gt;
&lt;p&gt;I understand the point about Chromebooks or Android being considered a Linux
desktop, but for me, a desktop includes the full freedom to change all aspects
of a system, including root access, packages, etc., all things that both
Chromebooks and Android try to make difficult. Historically, the things that
have made Linux difficult has been hardware compability. I believe that
problem has been somewhat exacerbated over the last few years as hardware
manufacturers want their devices available on Chromebooks and on servers. While
I&amp;rsquo;m not necessarily a fan of the &lt;a href=&#34;https://choosealicense.com/licenses/gpl-2.0/&#34;&gt;GPL v2 license&lt;/a&gt;
or the &lt;a href=&#34;https://en.wikipedia.org/wiki/Linux_kernel#Codebase&#34;&gt;tight tie between hardware drivers and the Linux kernel&lt;/a&gt;,
but from a market perspective I believe these two choices, plus the market
dominance of Linux, has put a lot of open source pressure on hardware vendors.&lt;/p&gt;
&lt;p&gt;In homes, a big barrier to adoption of Linux has been PC gaming. This
continues to be a big barrier today, and is one of the reasons why I hesitate
writing this post. I still use Windows when I want to crank up a game. In
offices, the biggest barrier has been Microsoft Office. There is
&lt;a href=&#34;https://www.libreoffice.org/&#34;&gt;LibreOffice&lt;/a&gt;, but it just isn&amp;rsquo;t 100% compatible
with Microsoft&amp;rsquo;s version. Those who know me know that I love to hate on
Microsoft Office (I&amp;rsquo;ve never run into another product that more consistently
does the wrong thing while trying to be &amp;ldquo;helpful&amp;rdquo;), but the quirks and sometimes
&lt;a href=&#34;https://www.joelonsoftware.com/2006/06/16/my-first-billg-review/&#34;&gt;crazy ancient bugs kept for backward compability sake&lt;/a&gt;
are hard to replicate. Another hesitation to writing this post - I basically
remote into a Windows box for access to Microsoft Office where I can write
Office documents, though I do generally &lt;strong&gt;read&lt;/strong&gt; them in LibreOffice.&lt;/p&gt;
&lt;p&gt;So, with those two caveats, in 2018 it &lt;strong&gt;has&lt;/strong&gt; been the year of Linux on
the desktop for me. I have two machines for regular use. At home, I use a 2015
Macbook Pro, technically able to boot macOS, but in reality hasn&amp;rsquo;t been booted
to that OS since mid-2018, and technically that wasn&amp;rsquo;t required either. I&amp;rsquo;m
considering wiping the MacOS portion of the machine entirely. On the road, I use an
&lt;a href=&#34;https://www.asus.com/us/Laptops/ASUS-Chromebook-Flip-C302CA/&#34;&gt;Asus Chromebook Flip C302CA&lt;/a&gt;.
That Chromebook can technically boot ChromeOS (with a Linux kernel), but again,
this hasn&amp;rsquo;t been booted to that operating system pretty much since I got it.&lt;/p&gt;
&lt;p&gt;It has been great, but I agree with others that this remains a bit of a
&amp;ldquo;only geeks need apply&amp;rdquo; endeavor. Mac hardware compatibility required some
tweaks and system setup. The Chromebook was much cleaner due to the hard
work of the folks at &lt;a href=&#34;https://galliumos.org/&#34;&gt;GalliumOS&lt;/a&gt;, but still, there
are config files and Google searches and etc., etc.&lt;/p&gt;
&lt;p&gt;I think the best sign, and least &amp;ldquo;only geeks need apply&amp;rdquo; situation, is the
presence of pre-installed Linux laptops. In definitely a particular order,
the &lt;a href=&#34;https://puri.sm/products/&#34;&gt;Librem 13/Librem 15&lt;/a&gt; has pre-installed
Linux on their devices. Here, you&amp;rsquo;re buying a premium Linux experience and
making a political statement that you value security and privacy. Laptops
from &lt;a href=&#34;https://system76.com/&#34;&gt;System76&lt;/a&gt;, aren&amp;rsquo;t quite as fanatical, but
still have security and privacy in mind while building. However, a good sign
that Linux is making slow, slow progress in the march to the mainstream is the
&lt;a href=&#34;https://www.dell.com/en-us/work/shop/cty/pdp/spd/xps-13-9370-laptop&#34;&gt;Dell XPS 13&lt;/a&gt;,
preloaded with either Windows or Ubuntu.&lt;/p&gt;
&lt;p&gt;For me, I&amp;rsquo;m super happy with my current travel laptop. While it&amp;rsquo;s a Chromebook,
it has no fan and charges from both sides - super useful when on a plane.
I&amp;rsquo;ve had some logistics problems with meetings, but never in a typical
&amp;ldquo;hey, put this on the projection screen&amp;rdquo;, for which I just pull out a Usb-C-&amp;gt;HDMI
converter like many newer laptops need, then reconfigure my display. Usually
the issues come up when they want me to also share with remote participants
&lt;strong&gt;and&lt;/strong&gt; are using some Windows only software. This is when I pull my
&amp;ldquo;remote into a Windows machine&amp;rdquo; cheater card.&lt;/p&gt;
&lt;p&gt;At home, I&amp;rsquo;m relatively comfortable. I still have some issues with video and
screen sharing in remote conferences. This is software dependent, but you might
be surprised by the number of conferencing systems that support Linux directly
or support web technologies. Otherwise, most stuff I do is on the web, or using
&lt;a href=&#34;https://www.docker.com/&#34;&gt;Docker&lt;/a&gt;, which is &lt;strong&gt;much&lt;/strong&gt; better on Linux.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested at all in trying it out, &lt;a href=&#34;http://www.ubuntu.com/&#34;&gt;Ubuntu&lt;/a&gt;
or &lt;a href=&#34;https://linuxmint.com/&#34;&gt;Mint&lt;/a&gt; are the most user-friendly ways to try it
out. Most people I know live on the web, in which case, it really doesn&amp;rsquo;t
matter which one you pick. Ubuntu is probably a bit more friendly, so if you
want the &amp;ldquo;easy button&amp;rdquo;, go for that. If I were installing for someone else,
I&amp;rsquo;d probably go with Mint. For myself, I think anything in the
&lt;a href=&#34;https://www.debian.org/&#34;&gt;Debian&lt;/a&gt; family has the most supportability, and if
you&amp;rsquo;re interested in a political statement, the core Debian only accepts
&amp;ldquo;free&amp;rdquo; software. I&amp;rsquo;ve played with other distros, but I think they&amp;rsquo;re a bit
too bleeding edge for folks looking for a &amp;ldquo;year of Linux on the desktop&amp;rdquo;. I
assume most of these people are looking for &amp;ldquo;give me pre-installed, or super-easy
self install, but cheaper than Windows&amp;rdquo; experience.&lt;/p&gt;
&lt;p&gt;So, caveats about games and Microsoft Office aside, I think that Linux is
much closer to the year of Linux on the desktop. The chasm has not yet been
crossed, and may never be, but it&amp;rsquo;s closer than any time in the past.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Minimal Golang System</title>
      <link>https://emil.lerch.org/minimal-golang-system/</link>
      <pubDate>Sat, 16 Dec 2017 09:45:30 -0800</pubDate>
      
      <guid>https://emil.lerch.org/minimal-golang-system/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been doing some experimentation with creating bare bones systems. These
come with a minimal of operational issues - fewer moving parts requires less
upkeep, have less code for security issues, etc.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://golang.org/&#34;&gt;Golang&lt;/a&gt; is a fantastic language for this, as it is
easy in Go to produce a statically linked binary (outside of Windows). Due
to the nature of Windows some dynamic linking is necessary. A static binary
(admittedly lacking C support) can be generated with a command similar to this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;CGO_ENABLED=0 GOOS=linux go build -a .
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To package, we&amp;rsquo;ll focus first on the container experience. &lt;a href=&#34;https://docker.com&#34;&gt;Docker&lt;/a&gt;
is ubiquituos at this point. &lt;a href=&#34;https://coreossomething&#34;&gt;rkt&lt;/a&gt;
is another alternative but this system is docker compatible so we&amp;rsquo;ll focus on
docker.&lt;/p&gt;
&lt;p&gt;The container runtime here does most of the heavy lifting for us. Containers
share the kernel of the operating system, so as long as the system is statically
linked, we don&amp;rsquo;t need much from the rest of the ecosystem. For a simple project
we need only the kernel and possibly (probably) some networking. The runtime
generally provides this, so we can get away with the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-Dockerfile&#34; data-lang=&#34;Dockerfile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt; scratch&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;COPY&lt;/span&gt; mybinary /mybinary&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;RUN&lt;/span&gt; mybinary&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This results in a super-small image with very little going on, even less than
&lt;a href=&#34;https://www.alpinelinux.org&#34;&gt;alpine linux&lt;/a&gt;. Of course, as with all things
container-based, we are dependent on the kernel used by the host OS. Sometimes,
we need ssl support, which requires additional infrastructure in the container.
For that, all we should need to do is copy the certs we need into the container
in the expected place:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;COPY ca-certificates.crt /etc/ssl/certs/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To achieve better isolation and build repeatability, we need only contain the
kernel being used. This, while significantly harder, is achievable. As this
will run on bare metal or a hypervisor, we will need to tailor the solution
to the underlying environment.&lt;/p&gt;
&lt;p&gt;The process was pretty well documented by earlier work, so I will instead focus
on the changes I&amp;rsquo;ve made to achieve a single-use virtual machine, rather than
an entire &lt;a href=&#34;https://sites.google.com/site/4utils/articles/minimal_linux_system/minimal-linux-system-from-scratch&#34;&gt;Linux from Scratch&lt;/a&gt;
built out. Before proceeding it&amp;rsquo;s worth skimming the link above to get a feel
of the process, at least up to the point of installing and configuring
busybox, which we won&amp;rsquo;t need. I&amp;rsquo;ll describe the differences here.&lt;/p&gt;
&lt;p&gt;First, sections 1 and 2 are interesting and work flawlessly but are unnecessary.
If you&amp;rsquo;re working with QEMU, you can simply create the disk image, attach that
image to another QEMU instance with a running Linux OS, and partition/format
the block device from within that instance. There is no need for all the fancy
loopback mounting and offset calculations. I feel going through the guide helped
me learn a lot, but if you just want to get things done and you have a Linux VM
already you can save a ton of time by simply partitioning/formatting within another
VM. With this process, you can also use the grub that&amp;rsquo;s likely already in your
Linux VM to copy stage1, stage2 and e2fs_stage1_5 over into the /boot/grub
directory on the target device (QEMU raw image file). You can also install
grub directly onto the device without going through the boot floppy procedure.&lt;/p&gt;
&lt;p&gt;I found the most difficult part of the process to be getting the right kernel
compilation options. I haven&amp;rsquo;t created a minimum configuration yet, but the
firecracker VM team has done quite a bit of work in this area, so starting
with &lt;a href=&#34;https://github.com/firecracker-microvm/firecracker/blob/master/resources/microvm-kernel-config&#34;&gt;their config&lt;/a&gt;
is a good starting point.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>First Thoughts on Rust</title>
      <link>https://emil.lerch.org/first-thoughts-on-rust/</link>
      <pubDate>Thu, 13 Jul 2017 16:43:28 -0700</pubDate>
      
      <guid>https://emil.lerch.org/first-thoughts-on-rust/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve read the &lt;a href=&#34;https://doc.rust-lang.org/book/&#34;&gt;Rust book&lt;/a&gt; a year ago but never actually programmed Rust in anger
until this past week. I intend to do more with &lt;a href=&#34;https://www.rust-lang.org/&#34;&gt;Rust&lt;/a&gt;, but wanted to document my
initial thoughts on the language as a consumer, partly for posterity, partly to
avoid stockholm syndrome, and partly I think it might be useful to anyone on
the Rust team interested in the &lt;a href=&#34;https://github.com/rust-lang/rust-roadmap/issues/3&#34; title=&#34;Rust should have a lower learning curve&#34;&gt;out of box experience with the language&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I believe that my background and experience is relevant, as I&amp;rsquo;ll discuss later
in some of these points. I&amp;rsquo;ve been coding as a student and professional over
the past 30 years in a variety of languages. I&amp;rsquo;ve dabbled in systems programming,
done a bit of embdedded, but for the most part stuck with application programming.
With that said&amp;hellip;&lt;/p&gt;
&lt;h2 id=&#34;the-good&#34;&gt;The Good&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Type inference&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I kept putting in type information, only later to take them out when I realized
the type inference is actually pretty awesome.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Borrow checker&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While I kept hearing a bunch of horror stories about fighting the borrow checker,
in my experience this was &lt;strong&gt;not&lt;/strong&gt; an issue. I just rolled through my coding
without too much issue and did not scratch my head much over borrow checker
errors.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compiler errors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The non-issues with the borrow checker might be due in part to the clarity of
the compiler errors. I found them downright enjoyable, and outside of them
actually fixing my code for me, I didn&amp;rsquo;t see much room for improvement. I think
the borrow checker problems people had were due in part to the old error messages,
but I think the &lt;a href=&#34;https://blog.rust-lang.org/2016/08/10/Shape-of-errors-to-come.html&#34;&gt;overhaul&lt;/a&gt;
in 1.12 might have been a large part of fixing the issues as well as the
perception.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pattern matching&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I &lt;strong&gt;love&lt;/strong&gt; working in a language with good pattern matching. I didn&amp;rsquo;t get a
chance to use it all that much (thanks partly to the ? operator), but in my
small amount of use it was a delight.&lt;/p&gt;
&lt;h2 id=&#34;the-weird&#34;&gt;The weird&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;unwrap()&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I found I was adding unwrap() &lt;strong&gt;everywhere&lt;/strong&gt;. I can&amp;rsquo;t say this is good or bad -
the nice thing about it is it forced me to understand where things could
blow up on me. The downside is that I had to type it, then read it, all the
time. This might be an area where Rust could improve with a bit of simple
syntactic sugar - just a character or something so I&amp;rsquo;m still forced to think
about what I&amp;rsquo;m doing without having to read past it every 15 seconds.&lt;/p&gt;
&lt;h2 id=&#34;things-that-struck-me-as-sub-optimal&#34;&gt;Things that struck me as sub-optimal&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Errors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of the most difficult challenges was around error handling. Clearly the
language itself has been struggling with this for some time. I had a small
function (10 LoC or so) that I wanted to read from a file and parse the data.
Either the read or the parsing could fail, and each would provide a different
Error type. Rust&amp;rsquo;s handling of a situation like this frankly sucks.  There is a
relatively new &lt;a href=&#34;https://github.com/rust-lang/rfcs/pull/243&#34; title=&#34;First-class error handling...&#34;&gt;? operator&lt;/a&gt; that is actually really quite cool. It
reduces a ton of boilerplate code and will basically either return an error
from the function if an error occurred during the call, or it will proceed.
Upon return from the call you&amp;rsquo;ll end up with a Result type that must be
unwrapped with unwrap(), but otherwise Bob&amp;rsquo;s your uncle. This works great
&lt;strong&gt;if&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You only do this once in a function&lt;/li&gt;
&lt;li&gt;You do it multiple times with the same error type&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you don&amp;rsquo;t follow either of these, the compiler has no way to infer the Error
type that could be returned. The only answer today is to write a &lt;strong&gt;ton&lt;/strong&gt; of
boilerplate. How much boilerplate? Well, there&amp;rsquo;s so much boilerplate that
the real solution today is to skip the whole process and just use the
third party crate &lt;a href=&#34;https://github.com/brson/error-chain&#34;&gt;error-chain&lt;/a&gt;. While
this is a great library, it should absolutely not be necessary. Also consider
it doesn&amp;rsquo;t solve all the boilerplate. I still needed:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#[macro_use]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;extern&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;crate&lt;/span&gt; error_chain;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;mod&lt;/span&gt; error {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; std;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;use&lt;/span&gt; serde_json;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    error_chain&lt;span style=&#34;color:#f92672&#34;&gt;!&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        foreign_links {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            IoError(std::io::Error);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            JsonError(serde_json::error::Error);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In my code. For those keeping score, that&amp;rsquo;s 11 lines of boilerplate code, &lt;strong&gt;and
an external crate&lt;/strong&gt; all to manage the fact that two things might error in a
function that currently consists of 7 lines of code. Folks, that be crazy.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Strings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I have a 100 LoC program, and even there I need 3 different string types.
What a pain! Here are the three types I used and why:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;str. This is a Rust primitive. It&amp;rsquo;s immutable and stack-allocated UTF-8.
Some functions require str.&lt;/li&gt;
&lt;li&gt;String. This is part of std. It&amp;rsquo;s mutable and heap allocated UTF-8. Some
functions require String, not str.&lt;/li&gt;
&lt;li&gt;OSString. Also part of std. It&amp;rsquo;s immutable and &amp;ldquo;a bunch of bytes as
provided by the OS&amp;rdquo;. Anything dealing with files, for instance, will need
this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At one point I literally ended up with this line of code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-rust&#34; data-lang=&#34;rust&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rc.insert(servicename.into_string().unwrap(), newestserviceentry.to_str().unwrap().to_string());
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That seems beyond silly. I get where Rust feels it&amp;rsquo;s required and I understand
that fundamentally all three string types serve a unique purpose, but can we
not do any better in 2010? At the very least the compiler should be able to
determine whether or not a string is being mutated and &amp;ldquo;do the right thing&amp;rdquo;,
thereby preserving Rust&amp;rsquo;s &amp;ldquo;zero cost abstraction&amp;rdquo; philosophy and removing one
of the string types. This constantly tripped me up.&lt;/p&gt;
&lt;h2 id=&#34;bottom-line&#34;&gt;Bottom Line&lt;/h2&gt;
&lt;p&gt;I think Rust has a bright future. The icky parts are fixable and/or livable,
and the roadmap is already looking to address some of the sharp edges. I look
forward to working with the language, though almost certainly in a primary way,
for a long time.&lt;/p&gt;
&lt;p&gt;For those interested, the code I wrote was to process AWS CLI model files
(the files used to describe all the service commands, parameters and help text -
the cli itself is basically just an execution engine). It can be found
as &lt;a href=&#34;https://github.com/elerch/cli-model-parser&#34;&gt;cli-model-parser&lt;/a&gt; on github
and it&amp;rsquo;s still pretty raw but handled what I needed at the time. I&amp;rsquo;ll be doing
at least a little bit more to it and might be doing a lot, depending on how
things go. This write-up was based on commits up to
&lt;a href=&#34;https://github.com/elerch/cli-model-parser/commit/f73e371e6fbe317b262de5c3688eb89949b0f296&#34;&gt;f73e371&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Rasperry Pi Headless Bootstrap</title>
      <link>https://emil.lerch.org/rasperry-pi-headless-bootstrap/</link>
      <pubDate>Thu, 09 Mar 2017 14:16:06 -0800</pubDate>
      
      <guid>https://emil.lerch.org/rasperry-pi-headless-bootstrap/</guid>
      <description>&lt;p&gt;Recently I picked up a new &lt;a href=&#34;https://www.raspberrypi.org/magpi/pi-zero-w/&#34;&gt;Rasberry Pi Zero W&lt;/a&gt;
and was excited but also lamenting the fact that I&amp;rsquo;d have to dig out a keyboard
and mouse. Being lazy, and being willing to work &lt;strong&gt;really hard&lt;/strong&gt; to remain
lazy, I was determined to find a way around this.&lt;/p&gt;
&lt;p&gt;I grabbed a MicroSD card and put &lt;a href=&#34;https://www.raspberrypi.org/downloads/raspbian/&#34;&gt;Raspian Jessie Lite&lt;/a&gt;
on it. I then &lt;a href=&#34;http://raspberrypi.stackexchange.com/questions/499/how-can-i-resize-my-root-partition&#34;&gt;extended the root partition&lt;/a&gt;
which I found easier to do before first boot since a) it was a virgin distro
install and b) since I wasn&amp;rsquo;t doing it from the running system there were no
reboots involved - I could simply eject the card and plug it back in to
refresh the block device listing.&lt;/p&gt;
&lt;p&gt;From there, I found a pretty good run-through of adding the ability to
connect via bluetooth here: &lt;a href=&#34;https://hacks.mozilla.org/2017/02/headless-raspberry-pi-configuration-over-bluetooth/&#34;&gt;https://hacks.mozilla.org/2017/02/headless-raspberry-pi-configuration-over-bluetooth/&lt;/a&gt;
The article is pretty good and though the script details are a little odd
I went with most of it. One big difference is that I did not allow automatic
login as I want to allow bluetooth serial connections permanently (so I can
add public hotspot networks later if I&amp;rsquo;m travelling). That was an easy change -
just remove the &amp;ldquo;-a pi&amp;rdquo; off the end of the ExecStart=/usr/bin/rfcomm line,
and it removes the need for the &amp;ldquo;Security&amp;rdquo; section near the bottom, since,
sure, you can find/pair/connect, but all you&amp;rsquo;ll get is a login prompt.
I followed the instructions in the article, so I won&amp;rsquo;t repeat them here,
but this is the script I used (with a generic device name) without the
&amp;ldquo;-a pi&amp;rdquo; on the getty line.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#!/bin/bash -e

# Display name in BT discovery list
echo PRETTY_HOSTNAME=MY-DEVICE-NAME

# Edit /lib/systemd/system/bluetooth.service to enable BT services
sudo sed -i: &amp;#39;s|^Exec.*toothd$| \
ExecStart=/usr/lib/bluetooth/bluetoothd -C \
ExecStartPost=/usr/bin/sdptool add SP \
ExecStartPost=/bin/hciconfig hci0 piscan \
|g&amp;#39; /lib/systemd/system/bluetooth.service

# create /etc/systemd/system/rfcomm.serfvice to enable BT serial from systemctl
sudo cat &amp;lt;&amp;lt;EOF | sudo tee /etc/systemd/system/rfcomm.service &amp;gt; /dev/null
[Unit]
Description=RFCOMM service
After=bluetooth.service
Requires=bluetooth.service

[Service]
ExecStart=/usr/bin/rfcomm watch hci0 1 getty rfcomm0 115200 vt100

[Install]
WantedBy=multi-user.target
EOF

#enable the new rfcomm service
sudo systemctl enable rfcomm

#start the rfcomm service
sudo systemctl restart rfcomm
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With the script in place to allow bluetooth I needed one more thing. The
terminal emulators for Android over bluetooth serial connections kind of suck,
so for that reason I wanted to be able to get in and get out of the bluetooth
side of things as quickly as I could, basically bootstrapping to wifi.&lt;/p&gt;
&lt;p&gt;I therefore created an additional script &lt;code&gt;new-wifi&lt;/code&gt; that looked like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;sudo iwlist wlan0 scan |grep -F SSID
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;read -p &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;SSID:&amp;#39;&lt;/span&gt; ssid
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;read -s -p &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;Password:&amp;#39;&lt;/span&gt; password
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo sh -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;wpa_passphrase &lt;/span&gt;$ssid&lt;span style=&#34;color:#e6db74&#34;&gt; &lt;/span&gt;$password&lt;span style=&#34;color:#e6db74&#34;&gt; | sed &amp;#39;/#psk/d&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  &amp;gt;&amp;gt; /etc/wpa_supplicant/wpa_supplicant.conf&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo wpa_cli reconfigure
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This handles networks that are broadcasting SSIDs just fine and doesn&amp;rsquo;t
expose passwords in &lt;code&gt;wpa_supplicant.conf&lt;/code&gt;. Make sure to chmod 700
on the script. For non-broadcast SSID networks, I&amp;rsquo;ll likely need to
modify the script to add the SSID as documented here:
&lt;a href=&#34;https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.md&#34;&gt;https://www.raspberrypi.org/documentation/configuration/wireless/wireless-cli.md&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;general-connection-overview-and-mac-specifics&#34;&gt;General Connection overview and Mac specifics&lt;/h2&gt;
&lt;p&gt;Next up, we want to connect to this thing. On Linux and Mac I found the
easiest way was to use screen. I didn&amp;rsquo;t try without specifying the baud rate
so I don&amp;rsquo;t know if that will work, but the general steps are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pair with the Pi&lt;/li&gt;
&lt;li&gt;Find the device&lt;/li&gt;
&lt;li&gt;(sometimes) rfcomm bind to the device&lt;/li&gt;
&lt;li&gt;screen &lt;!-- raw HTML omitted --&gt; 115200&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The baud rate should match the script above, so if you change it, just make
sure it matches. On mac, the device just &amp;ldquo;shows up&amp;rdquo; after pairing, so you&amp;rsquo;ll
look for it in /dev/cu.&lt;em&gt;MY-DEVICE-NAME&lt;/em&gt;-SerialPort. You&amp;rsquo;ll also see a
/dev/tty.&lt;em&gt;MY-DEVICE-NAME&lt;/em&gt;-SerialPort, but you&amp;rsquo;ll want to use the cu version.
There&amp;rsquo;s a great description of the differences here:
&lt;a href=&#34;http://stackoverflow.com/questions/8632586/macos-whats-the-difference-between-dev-tty-and-dev-cu&#34;&gt;http://stackoverflow.com/questions/8632586/macos-whats-the-difference-between-dev-tty-and-dev-cu&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;linux&#34;&gt;Linux&lt;/h2&gt;
&lt;p&gt;On Linux, something like this might also show up automagically. I like
to use arch linux when I can since nothing is automatic and therefore I
learn much more about the internals, so after pairing and making sure
my &lt;a href=&#34;https://wiki.archlinux.org/index.php/bluetooth&#34;&gt;system could work with bluetooth&lt;/a&gt;,
I was able to create a serial device using rfcomm. A great overview of this
process is in this gist: &lt;a href=&#34;https://gist.github.com/0/c73e2557d875446b9603&#34;&gt;https://gist.github.com/0/c73e2557d875446b9603&lt;/a&gt;
Note that the device becomes /dev/rfcomm0, but a simple &lt;code&gt;screen /dev/rfcomm0 115200&lt;/code&gt;
is all you need to connect. For me, I got &amp;lsquo;screen is terminating&amp;rsquo; when I tried
this. I then realized my user didn&amp;rsquo;t have permissions to read/write to /dev/rfcomm0.
So, I used &amp;lsquo;sudo screen&amp;hellip;&amp;rsquo; until I added myself to the uucp group. The uucp
group has rw permissions on the file, and you can add yourself with:
usermod -a -G uucp &lt;em&gt;myusername&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;android&#34;&gt;Android&lt;/h2&gt;
&lt;p&gt;On Android I needed an appy-app to connect to Bluetooth. As I mentioned earlier
they mostly suck as they&amp;rsquo;re usually optimized for non-terminal super-raw
usage. The two that I found the best was BlueTerm2 (a fork of BlueTerm that
uses volume buttons to send control characters instead of the jog wheel that
hasn&amp;rsquo;t existed in forever), and &lt;a href=&#34;https://play.google.com/store/apps/details?id=com.sena.bterm&amp;amp;hl=en&#34;&gt;SENA BTerm Bluetooth Terminal&lt;/a&gt;.
I&amp;rsquo;m not linking to BlueTerm2 here because I don&amp;rsquo;t want to send folks over there.
It&amp;rsquo;s mostly superior to SENA with one glaring issue - it doesn&amp;rsquo;t seem to be
able to send upper case characters. The source code is available so if someone
fixes it I&amp;rsquo;ll use BlueTerm2 in a heartbeat, but for now I&amp;rsquo;ll stick with SENA.&lt;/p&gt;
&lt;h2 id=&#34;windows&#34;&gt;Windows&lt;/h2&gt;
&lt;p&gt;For Windows, putty should be your friend as shown here:
&lt;a href=&#34;http://www.hobbyist.co.nz/?q=bluetooth-module-device&#34;&gt;http://www.hobbyist.co.nz/?q=bluetooth-module-device&lt;/a&gt;
I haven&amp;rsquo;t tried this though as I don&amp;rsquo;t have a use case for it and my Windows
machine doesn&amp;rsquo;t have a bluetooth adapter.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Terraform vs CloudFormation</title>
      <link>https://emil.lerch.org/terraform-vs-cloudformation/</link>
      <pubDate>Wed, 01 Mar 2017 14:17:24 -0800</pubDate>
      
      <guid>https://emil.lerch.org/terraform-vs-cloudformation/</guid>
      <description>&lt;p&gt;Recently there have been discussions about the advantages and disadvantegs
of using Hashicorp&amp;rsquo;s &lt;a href=&#34;https://www.terraform.io/&#34;&gt;Terraform&lt;/a&gt; vs
&lt;a href=&#34;https://aws.amazon.com/cloudformation/&#34;&gt;AWS CloudFormation&lt;/a&gt; for
infrastructure as code on AWS. While these products change continuously,
here&amp;rsquo;s a snapshot summarization of the advantages of each system.&lt;/p&gt;
&lt;h2 id=&#34;aws-cloudformation&#34;&gt;AWS Cloudformation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tighter integration with AWS Services:&lt;/strong&gt; In my opinion, this is the biggest
draw to using CloudFormation. You simply can&amp;rsquo;t use Terraform for things
like &lt;a href=&#34;https://aws.amazon.com/servicecatalog/&#34;&gt;AWS Service Catalog&lt;/a&gt;.
Service Catalog in particular is a huge benefit to acheiving agility with
control, and to avoid using it simply because your processes are
Terraform-based would be a shame.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IAM-based access control:&lt;/strong&gt; This is another example of the integration
story, but if you&amp;rsquo;re already heavily invested in AWS it&amp;rsquo;s nice to not
have to learn a different access control mechanism.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Has a GUI:&lt;/strong&gt; If you&amp;rsquo;re into that sort of thing, it&amp;rsquo;s nice to not be
forced to use the command line.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature support:&lt;/strong&gt; For new services and features, AWS CloudFormation
generally has support prior to Terraform. While there are exceptions to this,
fundamentally CloudFormation has an edge as the service is inside AWS
and thus has advance notice of all new services and features. Often
CloudFormation support is included with the launch of a new service or
feature. At the time of this writing, IPv6 VPC support is in CloudFormation
but Terraform does not yet support it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;terraform&#34;&gt;Terraform&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;count=&lt;em&gt;n&lt;/em&gt; and pre-built functions:&lt;/strong&gt; CloudFormation
has some functions, but Terraform goes further, and count=&lt;em&gt;n&lt;/em&gt; is
simply fantastic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inspect current state:&lt;/strong&gt; In many cases you shouldn&amp;rsquo;t need to guess
about your current environment, but if you&amp;rsquo;re mixing and matching
various templates or you&amp;rsquo;re the type of organization that occassionally
breaks the rules and allows some manual configuration, the ability
to define a template that can dynamically compensate for differences in
the environment is pretty powerful.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple account support:&lt;/strong&gt; You might be able to do this in CloudFormation,
but it&amp;rsquo;s complicated and a bit hackish.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple region support:&lt;/strong&gt; See above&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;mythical-advantage-of-terraform&#34;&gt;Mythical Advantage of Terraform&lt;/h2&gt;
&lt;p&gt;One advantage I &lt;strong&gt;hear&lt;/strong&gt; about Terraform but completely discard is the ability
to support multiple clouds. While this is true, the reality is that each
cloud works differently and makes different engineering tradeoffs, has
different feature sets, etc. In the end there&amp;rsquo;s no magic bullet for deploying
infrastructure seamlessly across different cloud service providers. You
&lt;strong&gt;might&lt;/strong&gt; be able to get away with an automated translation of one to another,
but you&amp;rsquo;ll either have to architect for the lowest common denominator or
you&amp;rsquo;ll lose something in translation. In the end organizations that try
always end up maintaining two sets of templates.&lt;/p&gt;
&lt;h2 id=&#34;other-approaches&#34;&gt;Other approaches&lt;/h2&gt;
&lt;p&gt;I do know of some folks that are using &lt;a href=&#34;https://github.com/cloudtools/troposphere&#34;&gt;Troposphere&lt;/a&gt;
with some success. This provides some benefits in terms of imperative
programming and the full power of Python.&lt;/p&gt;
&lt;h2 id=&#34;final-thoughts---for-now&#34;&gt;Final thoughts - for now&lt;/h2&gt;
&lt;p&gt;Most people I work with get by with CloudFormation in
&lt;a href=&#34;https://aws.amazon.com/about-aws/whats-new/2016/09/aws-cloudformation-introduces-yaml-template-support-and-cross-stack-references/&#34;&gt;YAML&lt;/a&gt;.
This keeps the implementation simple and allows full integration and (mostly)
faster resource support. Both Terraform and CloudFormation change often,
but this is the state of things on March 1st, 2017.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Moving AWS images into Govcloud</title>
      <link>https://emil.lerch.org/moving-aws-images-into-govcloud/</link>
      <pubDate>Mon, 09 Jan 2017 15:27:50 -0800</pubDate>
      
      <guid>https://emil.lerch.org/moving-aws-images-into-govcloud/</guid>
      <description>&lt;p&gt;If you have worked with &lt;a href=&#34;https://aws.amazon.com/govcloud-us/&#34;&gt;AWS GovCloud&lt;/a&gt;,
you know it is a very different region from most other AWS regions. It
requires a seperate account, linked to a standard AWS account, and uses
IAM users only - root users are not allowed at all. This has always been
a best practice, but in GovCloud, you have no choice.&lt;/p&gt;
&lt;p&gt;GovCloud also has fewer services than other regions. At the time of this
writing, &lt;a href=&#34;https://aws.amazon.com/marketplace/&#34;&gt;AWS Marketplace&lt;/a&gt; is one
of the services that is missing. This creates a problem in GovCloud
because many high quality AMIs are distributed for free but are only
available in Marketplace.&lt;/p&gt;
&lt;p&gt;To work around this, it is possible to move instances into GovCloud, though
it is a bit convoluted. First, before doing anything, make &lt;strong&gt;absolutely&lt;/strong&gt;
sure you are within the terms of the license agreement moving images
this way.&lt;/p&gt;
&lt;p&gt;Once the legality of doing this has been verified, the overall process
is straightforward but somewhat lengthy. Here I will cover specifics for
Linux, though Windows should follow the same high level approach.&lt;/p&gt;
&lt;h2 id=&#34;launch-your-instance-in-commercial&#34;&gt;Launch Your Instance in Commercial&lt;/h2&gt;
&lt;p&gt;You will want to actually launch the instance from the marketplace. At the
time of this writing, GovCloud has a single region in Oregon called
us-gov-west-1. You will want to be as physically close to the target
GovCloud region as possible for reasons that will become clear, so the
best place to launch your instance is us-west-2 (Oregon).&lt;/p&gt;
&lt;h2 id=&#34;configure-your-instance-in-commercial&#34;&gt;Configure your instance in Commercial&lt;/h2&gt;
&lt;p&gt;Next, you will want to configure your instance. This is an optional step.
As a proof of concept, I created a CentOS 7 base image from the
marketplace for this purpose. As a base image, I did not want to actually
configure it. What little was done (basically configuration of the ssh key)
would need to be undone if I want to make it useful to others. But you
can take this time to do whatever configuration you would like.&lt;/p&gt;
&lt;h2 id=&#34;stop-your-instance-in-commercial&#34;&gt;Stop your instance in Commercial&lt;/h2&gt;
&lt;p&gt;Next, we will stop this instance. What we are looking for is the underlying
EBS root volume (this procedure only works with EBS-backed AMIs).&lt;/p&gt;
&lt;h2 id=&#34;start-another-instance-in-commercial&#34;&gt;Start another instance in Commercial&lt;/h2&gt;
&lt;p&gt;Now, we can start another instance. We will need to attach two additional
EBS volumes after the fact. The first must be larger than our target
instance volume, the second attachment will be the target instance root
volume. At this point, you will should have 3 volumes on the instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(root)&lt;/li&gt;
&lt;li&gt;Large volume (xvdf)&lt;/li&gt;
&lt;li&gt;Root volume of target instance (xvdg)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;image-the-target-and-copy-to-image-to-govcloud&#34;&gt;Image the target and copy to image to GovCloud&lt;/h2&gt;
&lt;p&gt;With the volumes attached (but not mounted), we will want to get an image
file of the root volume. This is the &lt;strong&gt;whole volume&lt;/strong&gt;, not an individual
partition. Likewise, for the throwaway &amp;ldquo;large volume&amp;rdquo;, you do not really
need to partition it as we will be discarding it soon. The commands look
like this:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo mkfs -t ext4 /dev/xvdf
sudo mount /dev/xvdf /mnt
sudo dd if=/dev/xvdg of=/mnt/myimage.img bs=1M
aws s3 cp /mnt/myimage.img s3://yourgovcloudbucket/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that prior to the aws s3 command you should be doing an aws configure
command to setup credentials (access key/secret key) for GovCloud.&lt;/p&gt;
&lt;p&gt;At this point, you have a bit-for-bit copy of your commerical image in
a GovCloud S3 bucket, so the data is in the correct region. Everything
in commercial can now be terminated/deleted.&lt;/p&gt;
&lt;h2 id=&#34;rehydrate-image-in-govcloud&#34;&gt;Rehydrate image in GovCloud&lt;/h2&gt;
&lt;p&gt;We will want to now launch an instance in GovCloud to finish this
process. In addition we will want two additional EBS volumes in the
same manner as described above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(root)&lt;/li&gt;
&lt;li&gt;Large volume (xvdf)&lt;/li&gt;
&lt;li&gt;Root volume of target instance (xvdg)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The third volume should be the exact size of the original volume from
the commercial region. Once launched with EBS volumes attached, we
can re-hydrate the volume to make it bootable once again:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo mkfs -t ext4 /dev/xvdf
sudo mount /dev/xvdf /mnt
# Note you might need permissions to create/write files to /mnt
aws s3 cp s3://yourgovcloudbucket/myimage.img /mnt 
sudo dd if=/mnt/myimage.img of=/dev/xvdg bs=1M
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At this point, this instance can be shut down and the large EBS volume
(formerly known as xvdf) can be deleted. However, we want to create
a snapshot of the rehydrated root volume of the target instance
(formerly known as xvdg). From this snapshot, we can use the console
or the command line to create an AMI. From the console, right click the
EBS Snapshot and choose &amp;ldquo;Create Image&amp;rdquo;.&lt;/p&gt;
&lt;h2 id=&#34;done&#34;&gt;Done&lt;/h2&gt;
&lt;p&gt;And&amp;hellip;we are done. One thing to note is that unless you have reconfigured
the instance you will need to log in with the ssh key you originally
specified in the commercial region as that key will now be &amp;ldquo;baked in&amp;rdquo;
to the AMI.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Installation of Arch Linux on a USB stick with UEFI and legacy BIOS Support</title>
      <link>https://emil.lerch.org/installation-of-arch-linux-on-a-usb-stick-with-uefi-and-legacy-bios-support/</link>
      <pubDate>Tue, 01 Nov 2016 19:28:18 -0700</pubDate>
      
      <guid>https://emil.lerch.org/installation-of-arch-linux-on-a-usb-stick-with-uefi-and-legacy-bios-support/</guid>
      <description>&lt;p&gt;I tend to move around machines quite a bit, especially when traveling.
As such, I thought it would be useful to have a portable environment on
a USB stick. Since I don&amp;rsquo;t know what type of machine I would be walking
up to, this needed to support UEFI and BIOS. I wanted an actual &lt;strong&gt;install&lt;/strong&gt;
on a USB stick, not simply a live environment.&lt;/p&gt;
&lt;p&gt;I chose Arch linux because I like the lightweight do-it-yourself
philosophy and had heard good things about the pacman package manager.
I had some previous experience with it as well and it was overall a positive
experience. I remain concerned that the overall linux community doesn&amp;rsquo;t
consider arch, so updates may break things. Time will tell on this.&lt;/p&gt;
&lt;p&gt;It took a while and I learned a &lt;strong&gt;ton&lt;/strong&gt;, including that certain hardware
&lt;a href=&#34;https://www.amazon.com/review/R2VFHAYPJIC9YB/ref=cm_cr_rdp_perm&#34;&gt;will not work&lt;/a&gt;
with at least the UEFI on Macs. Those that are interested, btw, should
use the &lt;a href=&#34;https://www.amazon.com/Samsung-Flash-Drive-MUF-64BB-AM/dp/B013CCTNKU/ref=cm_cr-mr-title&#34;&gt;Samsung Flash Drive Fit&lt;/a&gt;
instead.&lt;/p&gt;
&lt;p&gt;I created a &lt;a href=&#34;https://gist.github.com/elerch/678941eb670324ffc3f261eabba81310&#34;&gt;gist of my efforts&lt;/a&gt;,
which I&amp;rsquo;ll continue to maintain as a living document. I will not,
however, include packages I install for my own use so I can keep
the gist as more of a base install step-by-step.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also working on making my USB multi-boot, but most other Linux
distros assume during install that they should install a bootloader,
etc., which would mess with my setup. I have a plan, however, so
stay tuned&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Running Windows 10 on AWS EC2</title>
      <link>https://emil.lerch.org/running-windows-10-on-aws-ec2/</link>
      <pubDate>Tue, 19 Apr 2016 14:11:16 -0700</pubDate>
      
      <guid>https://emil.lerch.org/running-windows-10-on-aws-ec2/</guid>
      <description>&lt;p&gt;Getting Windows 10 on EC2 isn&amp;rsquo;t difficult, but perusing the &lt;a href=&#34;http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UsingImportImage.html&#34;&gt;documentation&lt;/a&gt;
can lead to confusion.&lt;/p&gt;
&lt;p&gt;You can&amp;rsquo;t mount an ISO to an empty VM the way you might do in &lt;a href=&#34;https://www.virtualbox.org/&#34;&gt;VirtualBox&lt;/a&gt;,
so this process requires a local copy of the VM to be created, then
using the aws ec2 import-image command to create the AMI. When done,
not only will the image be ready for EC2, but it will be detected as
Windows by AWS and be configured such that it has many of the same
AWS-specific features as other Windows AMIs provided by Amazon.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new Windows VM using VirtualBox
&lt;ul&gt;
&lt;li&gt;Make sure to choose VHD as the disk image format&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Install Windows 10 within the VirtualBox VM as normal.
Note that a fresh install will format the VHD as MBR,
which is the only partition format supported on AWS&lt;/li&gt;
&lt;li&gt;Shut down the VM and upload the vhd file to an S3 bucket&lt;/li&gt;
&lt;li&gt;Create a json file describing the VHD. This usually looks something like this:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[{
  &amp;#34;Description&amp;#34;: &amp;#34;Windows 10 Base Install&amp;#34;,
  &amp;#34;Format&amp;#34;: &amp;#34;vhd&amp;#34;,
  &amp;#34;UserBucket&amp;#34;: {
    &amp;#34;S3Bucket&amp;#34;: &amp;#34;mybucket&amp;#34;,
    &amp;#34;S3Key&amp;#34;: &amp;#34;Win10.vhd&amp;#34;
  }
}]
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Import using the CLI: aws ec2 import-image &amp;ndash;description &amp;ldquo;Windows 10&amp;rdquo; &amp;ndash;disk-containers file://containers.json&lt;/li&gt;
&lt;li&gt;The import task will have been created, but the actual import will
take a while. To check progress, use:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;aws ec2 describe-import-image-tasks
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Once the AMI created, start instance as normal using your new ami&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Update 2016-05-10&lt;/strong&gt;: If you want to remote in, don&amp;rsquo;t forget to setup the
OS to allow remote desktop prior to copying the VHD!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Consider a single container per virtual machine in production</title>
      <link>https://emil.lerch.org/consider-a-single-container-per-virtual-machine-in-production/</link>
      <pubDate>Tue, 16 Feb 2016 13:35:02 -0800</pubDate>
      
      <guid>https://emil.lerch.org/consider-a-single-container-per-virtual-machine-in-production/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Software_container&#34;&gt;Containers&lt;/a&gt; are a great unit of deployment. They&amp;rsquo;re a great way to isolate
code, reduce attack areas, and, well, &lt;strong&gt;contain&lt;/strong&gt; a service. When it comes
to deployment in production, operational attributes of containers must be
considered.&lt;/p&gt;
&lt;p&gt;Container technology can enable significant density (described in terms of
containers per vm) while retaining isolation between services. However, is
this something we want to take advantage of operationally? Two significant
issues with pushing for &amp;gt;1 container per vm come to mind.&lt;/p&gt;
&lt;p&gt;First, for security-sensitive workloads, container technology provides a
lesser security model than virtual machines on a hypervisor. A shared
kernel forces us to trust user mode container orchestration tools (e.g.
&lt;a href=&#34;https://www.docker.com/&#34;&gt;Docker&lt;/a&gt;, the daemon of which still runs as root) and kernel level
constructs (e.g. &lt;a href=&#34;https://en.wikipedia.org/wiki/Cgroups&#34;&gt;cgroups&lt;/a&gt; and &lt;a href=&#34;https://en.wikipedia.org/wiki/Linux_namespaces&#34;&gt;namespaces&lt;/a&gt;). Regardless of your feelings of
the maturity level of this code, the fact of the matter is that there is
simply a larger attack surface.&lt;/p&gt;
&lt;p&gt;For this reason, only containers that implicitly trust each other can
feel free to share a virtual machine in production. However, should they?
The second consideration here is performance. For years we&amp;rsquo;ve been moving
as an industry to more granular, single purpose virtual machines. A mail
server, a web server with a single app or service, a database server, etc.
Does the logic here really change with the introduction of containers? I
believe the same reasons that push us toward single purpose virtual machines
still hold true for containers. It becomes exponentially more difficult
to deploy and manage virtual machines that contain mixed workloads. In
addition, you can no longer tailor the characteristics (network, disk, ram,
cpu) of the virtual machine to the workload, but need to pick general
configurations.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Xen on AWS EC2</title>
      <link>https://emil.lerch.org/xen-on-aws-ec2/</link>
      <pubDate>Mon, 08 Feb 2016 14:19:43 -0800</pubDate>
      
      <guid>https://emil.lerch.org/xen-on-aws-ec2/</guid>
      <description>&lt;p&gt;Since I&amp;rsquo;m working for &lt;a href=&#34;https://aws.amazon.com&#34;&gt;AWS&lt;/a&gt;, I want to understand fundamentally the workings
of the open source &lt;a href=&#34;http://xenproject.org&#34;&gt;Xen Hypervisor&lt;/a&gt;. I also want to dig more deeply into
the emerging &lt;a href=&#34;https://en.wikipedia.org/wiki/Unikernel&#34;&gt;Unikernel&lt;/a&gt; ecosystem. Of course, I want to do this on
&lt;a href=&#34;https://aws.amazon.com/ec2/&#34;&gt;Amazon EC2&lt;/a&gt;, because generally I prefer to assume my laptop is ephemeral and
could be lost, stolen, dropped, etc. However, Xen doesn&amp;rsquo;t nest well, so putting
Xen in a virtual machine on top of Xen is a little bit crazy-talk.&lt;/p&gt;
&lt;p&gt;Undaunted, I searched around, finally bumping into an old research project
at Cornell called &lt;a href=&#34;https://code.google.com/archive/p/xen-blanket/&#34;&gt;Xen Blanket&lt;/a&gt; that aims to do just this. The &lt;a href=&#34;http://xcloud.cs.cornell.edu/code/README.txt&#34;&gt;instructions&lt;/a&gt;
are old for an old version of Xen and CentOS. So old that it took me two
runs to get it right. This &lt;a href=&#34;http://www.cs.cornell.edu/courses/cs6410/2013fa/lab0b.htm&#34;&gt;course lab&lt;/a&gt; was more explicit and ultimately I
compared the directions and kind of ran through both sets of instructions
together.&lt;/p&gt;
&lt;p&gt;At the end of the day, I ended up with an older version of CentOS with an
older version of Xen installed. This was all in us-east-1 (Virginia) region,
and since I live in Oregon I migrated the AMI over to us-west-2 (Oregon).
If you would like to play around with Xen for Unikernel development or
any other reason, I&amp;rsquo;ve created AMIs in us-east-1 and us-west-2 and made
them public. Once launched, use your ssh key with the &amp;lsquo;root&amp;rsquo; user.&lt;/p&gt;
&lt;p&gt;The following links will start the launch wizard in the console with these AMIs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;us-east-1 AMI (ami-fd2a0197): &lt;a href=&#34;https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#LaunchInstanceWizard:ami=ami-fd2a0197&#34;&gt;https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#LaunchInstanceWizard:ami=ami-fd2a0197&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;us-west-2 AMI (ami-bf2fcedf): &lt;a href=&#34;https://console.aws.amazon.com/ec2/v2/home?region=us-west-2#LaunchInstanceWizard:ami=ami-bf2fcedf&#34;&gt;https://console.aws.amazon.com/ec2/v2/home?region=us-west-2#LaunchInstanceWizard:ami=ami-bf2fcedf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>index.html behavior with S3 and Cloudfront</title>
      <link>https://emil.lerch.org/index.html-behavior-with-s3-and-cloudfront/</link>
      <pubDate>Tue, 02 Feb 2016 16:53:08 -0800</pubDate>
      
      <guid>https://emil.lerch.org/index.html-behavior-with-s3-and-cloudfront/</guid>
      <description>&lt;p&gt;index.html is an interesting beast in S3. S3 is an object store. It is often
mistaken for a filesystem, but it is not. It is also &lt;strong&gt;not&lt;/strong&gt; a web server,
though it can &lt;a href=&#34;http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html&#34;&gt;pretend to be&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;CloudFront is a CDN, and as such, it is also not a web server, though it
does serve web content to users. All this makes for a strange situation for
our friend, index.html.&lt;/p&gt;
&lt;p&gt;index.html is generally used as a default document in web servers. So, if I
visit &lt;a href=&#34;https://example.com/&#34;&gt;https://example.com/&lt;/a&gt; (and you are using SSL, right?), the web server on
example.com will search the filesystem for a file named index.html in the root
directory. Assuming it exists, the contents of that file will be read and sent
to the browser. This behavior also applies to subdirectories, so if the
directory on the server is at /var/www and you have a file /var/www/my-test/index.html,
the url &lt;a href=&#34;https://example.com/my-test/&#34;&gt;https://example.com/my-test/&lt;/a&gt; will operate and display the contents
of the file.&lt;/p&gt;
&lt;p&gt;Because this behavior is so common, S3 website hosting mimicks this behavior.
This is fantastic, but also confuses things when using CloudFront with an
&lt;a href=&#34;http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html&#34;&gt;S3 Origin&lt;/a&gt;. One might expect index.html behavior to work the same way, and in
a way, it does. If I setup CloudFront for the domain example.com and visit
&lt;a href=&#34;https://example.com/&#34;&gt;https://example.com/&lt;/a&gt;, the index.html file will be fetched from S3 by CloudFront
and delivered as we expect. However, this behavior is a one-off by CloudFront.
If I visit &lt;a href=&#34;https://example.com/my-test/&#34;&gt;https://example.com/my-test/&lt;/a&gt;, the object in my bucket with the
key my-test/index.html will &lt;strong&gt;not&lt;/strong&gt; be fetched. As a result, you&amp;rsquo;ll simply
get a 404.&lt;/p&gt;
&lt;p&gt;How do we get around this? The first and easiest way is to simply create links
to my-test/index.html rather than my-test/. This makes for ugly URLs though.
Alternatively, we can treat S3 as a custom origin. From CloudFront&amp;rsquo;s perspective,
it&amp;rsquo;s now treating S3 as it would treat any web server that might be doing any
kind of processing. This works great, but comes with a couple downsides.
First, you can&amp;rsquo;t lock down your S3 Bucket, so people could bypass the CDN
and hit the S3 bucket URL directly. Secondly, S3 website hosting doesn&amp;rsquo;t
support SSL, so you&amp;rsquo;re left with a solution that is not encrypted end to end.
It&amp;rsquo;s possible, though highly unlikely, that a man in the middle could alter
your content between S3 and the CloudFront edge location. I hope at some
point S3 website hosting supports SSL and eliminates this problem.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Static Site Deployment with &#39;git push&#39; to GitHub</title>
      <link>https://emil.lerch.org/static-site-deployment-with-git-push-to-github/</link>
      <pubDate>Mon, 01 Feb 2016 13:40:16 -0800</pubDate>
      
      <guid>https://emil.lerch.org/static-site-deployment-with-git-push-to-github/</guid>
      <description>&lt;p&gt;The process I&amp;rsquo;ve put together for publishing this blog allows for automatic
publish to the web as soon as I git commit/git push. This post describes
how this is done.&lt;/p&gt;
&lt;p&gt;As background, this blog is hosted on Amazon Web Services&amp;rsquo; &lt;a href=&#34;http://aws.amazon.com/s3/&#34;&gt;S3&lt;/a&gt; service with
CDN capabilities and SSL termination provided by &lt;a href=&#34;http://aws.amazon.com/cloudfront/&#34;&gt;CloudFront&lt;/a&gt; and &lt;a href=&#34;https://aws.amazon.com/certificate-manager/&#34;&gt;Amazon
Certificate Manager&lt;/a&gt;. This last service is extremely new, to the point that
I obtained and assigned the certificate to CloudFront &lt;a href=&#34;https://aws.amazon.com/blogs/aws/new-aws-certificate-manager-deploy-ssltls-based-apps-on-aws/&#34;&gt;the very day CloudFront
integration was available&lt;/a&gt;.
As such this was a last minute change. My original intent was to use
&lt;a href=&#34;https://letsencrypt.org/&#34;&gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; free SSL certificates, but since ACM is also free and the
service renews certs automatically, this seemed to be the better way to go.&lt;/p&gt;
&lt;p&gt;With hosting taken care of, my next goal was to automate publishes. I keep
the actual content of the blog on &lt;a href=&#34;https://github.com&#34;&gt;GitHub&lt;/a&gt; at &lt;a href=&#34;https://github.com/elerch/blog&#34;&gt;https://github.com/elerch/blog&lt;/a&gt;.
GitHub has integration with AWS &lt;a href=&#34;https://aws.amazon.com/sns/&#34;&gt;SNS&lt;/a&gt;, so I can trigger a &lt;a href=&#34;https://aws.amazon.com/lambda/&#34;&gt;Lambda&lt;/a&gt; function
based on a push to GitHub (or a commit through the web). The process looks
like this: &lt;a href=&#34;https://aws.amazon.com/blogs/compute/dynamic-github-actions-with-aws-lambda/&#34;&gt;https://aws.amazon.com/blogs/compute/dynamic-github-actions-with-aws-lambda/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In my lambda function, I use the &lt;a href=&#34;https://developer.github.com/v3/&#34;&gt;GitHub REST API&lt;/a&gt; to get the &lt;a href=&#34;https://developer.github.com/v3/repos/contents/#get-archive-link&#34;&gt;repo archive&lt;/a&gt;.
From there, I can unpack the archive and copy it to S3, using &lt;a href=&#34;http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingRRS.html&#34;&gt;Reduced
Redundancy Storage&lt;/a&gt; to save a few portions of a cent. Outside the cost
of S3, the whole process stays within free usage limits.&lt;/p&gt;
&lt;p&gt;The lambda function, &lt;a href=&#34;https://aws.amazon.com/cloudformation/&#34;&gt;CloudFormation&lt;/a&gt; templates, and various scripts are
hosted on GitHub: &lt;a href=&#34;https://github.com/elerch/blog-deploy/&#34;&gt;https://github.com/elerch/blog-deploy/&lt;/a&gt;. I&amp;rsquo;ve also
created this diagram on &lt;a href=&#34;https://cloudcraft.co/&#34;&gt;CloudCraft&lt;/a&gt;:&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;./posts/2016-02-01/blog-deployment.png&#34;&gt;&lt;img src=&#34;./posts/2016-02-01/blog-deployment.png&#34;
         alt=&#34;Deployment&#34;/&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;One thing I&amp;rsquo;d really like to do in the lambda function is pull out the
tight integration between the themes and the lambda function itself.
However, since I don&amp;rsquo;t normally &lt;a href=&#34;https://github.com/elerch/gindoro&#34;&gt;change my theme&lt;/a&gt;,
this will wait for another day. It would also be nice to extract the
actual build steps (extract archive/run hugo on extracted archive/
copy themes/copy output to S3) so that the lambda function can be used
for a wider array of &lt;a href=&#34;https://en.wikipedia.org/wiki/DevOps&#34;&gt;DevOps&lt;/a&gt; scenarios.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>blog comments</title>
      <link>https://emil.lerch.org/blog-comments/</link>
      <pubDate>Mon, 01 Feb 2016 11:54:14 -0800</pubDate>
      
      <guid>https://emil.lerch.org/blog-comments/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve decided to add &lt;a href=&#34;https://disqus.com&#34;&gt;Disqus&lt;/a&gt; comments to the site. Having a blog without
comments is just&amp;hellip;not a blog. That said, I&amp;rsquo;m not particularly happy with
the amount of overhead it adds to the page. My base configuration (no
images, no comments) involves a total of two requests to the site for
full rendering (three if you count favicon business, which I don&amp;rsquo;t).
The blog is delivered via AWS &lt;a href=&#34;https://aws.amazon.com/s3/&#34;&gt;S3&lt;/a&gt; and &lt;a href=&#34;https://aws.amazon.com/cloudfront/&#34;&gt;CloudFront&lt;/a&gt;, which gives me &lt;a href=&#34;https://en.wikipedia.org/wiki/Content_delivery_network&#34;&gt;CDN&lt;/a&gt;
capabilities. Since I&amp;rsquo;m doing static generation, the site is pretty quick.
With Disqus, my request count balloons to 33 requests with a default
Disqus setup. I went through Disqus and turned off as much tracking
and things as I could, but I&amp;rsquo;m still at a 26 request baseline.
Hopefully there are some settings to make this more minimal, since I&amp;rsquo;m
new to Disqus I haven&amp;rsquo;t seen what I can tweak.&lt;/p&gt;
&lt;p&gt;To keep the site minimal, I&amp;rsquo;ve configured Disqus only on the full post,
&lt;strong&gt;not&lt;/strong&gt; the summary page. As an aside, I also don&amp;rsquo;t run any tracking
software (e.g. Google Analytics). First, I don&amp;rsquo;t want the overhead, and
second, I don&amp;rsquo;t care to track my readers.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Moving to new site</title>
      <link>https://emil.lerch.org/2016-02-01-moving-to-new-site/</link>
      <pubDate>Mon, 01 Feb 2016 11:35:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2016-02-01-moving-to-new-site/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ll be continuing my site off the blogger platform, but retaining this
site for historical purposes. Please follow me to my new home at
&lt;a href=&#34;https://emil.lerch.org/&#34;&gt;https://emil.lerch.org/&lt;/a&gt;. My first post goes into more detail about the
move.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Home Backup Strategy</title>
      <link>https://emil.lerch.org/home-backup-strategy/</link>
      <pubDate>Mon, 07 Dec 2015 16:09:36 -0800</pubDate>
      
      <guid>https://emil.lerch.org/home-backup-strategy/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been meaning to document my home backup strategy for quite some time.
In the process of evolving the design, I&amp;rsquo;ve tried to address the following
concerns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rapid restoration of data in the event of an outage (RTO)&lt;/li&gt;
&lt;li&gt;Minimal data loss from an incident (RPO)&lt;/li&gt;
&lt;li&gt;Recovery from accidental deletes&lt;/li&gt;
&lt;li&gt;Recovery from malicious deletes, such as ransomware&lt;/li&gt;
&lt;li&gt;Recovery from cosmic ray damage on hard drive platters&lt;/li&gt;
&lt;li&gt;Recovery from total destruction of the house&lt;/li&gt;
&lt;li&gt;Recovery from a failed hard drive&lt;/li&gt;
&lt;li&gt;Nice, but not too expensive. Contain recurring costs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are all interrelated, so I cannot describe a single technology or
procedure that covers each item in turn. Instead, I&amp;rsquo;ll describe the solution
and discuss how it addresses each point.&lt;/p&gt;
&lt;p&gt;The solution I&amp;rsquo;ve evolved uses a local &lt;a href=&#34;https://en.wikipedia.org/wiki/Network-attached_storage&#34;&gt;NAS&lt;/a&gt; with continous asynchronous backup
to Code42&amp;rsquo;s &lt;a href=&#34;http://www.code42.com/crashplan/&#34;&gt;CrashPlan&lt;/a&gt; servers in the cloud. This is the only portion of the
solution that includes a recurring cost, however, that could be eliminated if
I instead used their &amp;ldquo;backup to a friend&amp;rdquo; feature. The $5/month cost
is reasonable, especially for unlimited data. The continuous asynchronous
backup feature is nice in that it provides minimal data loss in the case of
an issue. Potential data loss is limited to the time it takes any new/changed files
to transfer to the Internet. CrashPlan also provides access to previous versions
of files, so if ransomware encrypts my data or I accidentally delete a file,
I&amp;rsquo;m covered. It can also protect me from cosmic ray damage in certain
scenarios (basically, if I knew the damage had occurred). The downside,
however, is that this does not protect me from cosmic rays, nor does it allow
me a quick recovery time in the event of a loss of my data locally.&lt;/p&gt;
&lt;p&gt;To deal with the as yet unaddressed concerns, I built a local NAS server using
Ubuntu and the &lt;a href=&#34;http://zfsonlinux.org/&#34;&gt;ZFS&lt;/a&gt; file system. I&amp;rsquo;m interested in &lt;a href=&#34;https://btrfs.wiki.kernel.org/index.php/Main_Page&#34;&gt;btrfs&lt;/a&gt; as a replacement,
but this is still experimental. ZFS has a lot of really cool features. One of
the best is also in btrfs, and that feature allows stored data to reheal through
checksums. This is in contrast to a RAID system which is focused on one or
more drive failures. In my ZFS &amp;ldquo;pool&amp;rdquo;, I have created the equivalent of a
software RAID level 5 called RAID-Z. This requires that I use 3 hard drives
and can tolerate a failure of 1 hard drive at a time without losing data.&lt;/p&gt;
&lt;p&gt;This setup has (un)fortunately been tested. When I built the array, I purchased
three hard drives at the same time. This is generally not-advised, as the drives
therefore come from the same batch and are therefore more likely to fail at the
same time. This particular batch did have problems, but I was lucky to lose
one drive at a time over the course of a year. At no time did I lose any data,
although I did come close, mostly due to my own misunderstanding of the
problem at the time.&lt;/p&gt;
&lt;p&gt;Due to my use of /dev/sdb, /dev/sdc, etc naming, the drive order changed and I
repartitioned the incorrect job when I already had a failed drive. I learned the
hard way to use disk ids when assigning the drives to the ZFS pools. However,
I learned more about ZFS and came up with another way to reduce the recovery
time in the event of a loss of the array. Now my physical drives have printed
labels on the outside containing the disk id so if a failure occurs it&amp;rsquo;s easy
to determine the correct drive to yank from the system.&lt;/p&gt;
&lt;p&gt;ZFS has a feature called snapshots, which are excellent for marking (and maybe
reverting to) a point of time of the system. Since ZFS is a &lt;a href=&#34;https://en.wikipedia.org/wiki/Copy-on-write&#34;&gt;COW&lt;/a&gt; (Copy on
Write) filesystem, snapshots do not take disk space until you start to change
data. To limit the damage should I get hit with ransomware, I had already setup
weekly snapshots on my ZFS. I don&amp;rsquo;t change a ton of data since the setup is
mostly for pictures and videos of the kids, so I stayed with weekly. However,
snapshots have another great use. You can &amp;ldquo;send&amp;rdquo; and &amp;ldquo;receive&amp;rdquo; deltas of your
data between ZFS pools without having to resort to any synchronization software.
Armed with this knowledge, I purchased a USB drive with enough storage space
to hold my data, created a ZFS pool with the single drive (this isn&amp;rsquo;t about
redundancy - this is about minimizing down time in a catastrophic loss to the
main array), and I can now send my deltas to the external drive. I do not have
this on a schedule yet, but I should get this cron job up shortly.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>New Blog</title>
      <link>https://emil.lerch.org/new-blog/</link>
      <pubDate>Mon, 30 Nov 2015 09:22:24 -0800</pubDate>
      
      <guid>https://emil.lerch.org/new-blog/</guid>
      <description>&lt;p&gt;I am transitioning to a new blog host and new, well, everything
regarding my blog. I had the following goals when creating this new blog site:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove dependence on blogger.com&lt;/li&gt;
&lt;li&gt;Change the url. It&amp;rsquo;s now more common to &lt;strong&gt;not&lt;/strong&gt; have &amp;ldquo;blog&amp;rdquo; in the url&lt;/li&gt;
&lt;li&gt;Reduce the page size/number of requests&lt;/li&gt;
&lt;li&gt;Improve the speed of the site (see above, plus CDN)&lt;/li&gt;
&lt;li&gt;SSL everywhere&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is still a work in progress and the styling will be updated
moving forward, but I&amp;rsquo;m a fan of dark themes, so this is roughly where
I&amp;rsquo;m going.&lt;/p&gt;
&lt;p&gt;As part of this change, I&amp;rsquo;ve embraced &lt;a href=&#34;https://github.com/spf13/hugo&#34;&gt;hugo&lt;/a&gt;
as a static site generator. I like hugo for a number of reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s fast&lt;/li&gt;
&lt;li&gt;It requires no third party software (like &lt;a href=&#34;https://jekyllrb.com/&#34;&gt;Jekyl&lt;/a&gt; requires Ruby). Important since I might author in Windows, Mac, or online&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s written in &lt;a href=&#34;https://golang.org/&#34;&gt;golang&lt;/a&gt;, which appeals to my inner tech core&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s easy to use, especially with github gist support in 0.15!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My old authoring process was completely online in blogger. This site
is now in &lt;a href=&#34;https://github.com/elerch/blog&#34;&gt;github&lt;/a&gt;, so I can still author
online or offline at any time. Publishing is still a work in progress
and will be fodder for another post.&lt;/p&gt;
&lt;h1 id=&#34;things-im-thrilled-about&#34;&gt;Things I&amp;rsquo;m thrilled about&lt;/h1&gt;
&lt;p&gt;The size/speed of the site is fantastic. When I host the site I&amp;rsquo;ll
put things on a CDN as well, but I&amp;rsquo;m glad to go from 94 requests (and console errors):
&lt;figure&gt;&lt;a href=&#34;./posts/2015-11-30/before.png&#34;&gt;&lt;img src=&#34;./posts/2015-11-30/before.png&#34;
         alt=&#34;Before&#34;/&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;To 2:
&lt;figure&gt;&lt;a href=&#34;./posts/2015-11-30/after.png&#34;&gt;&lt;img src=&#34;./posts/2015-11-30/after.png&#34;
         alt=&#34;After&#34;/&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;I also get to remove the dependency on &lt;a href=&#34;http://alexgorbatchev.com/SyntaxHighlighter/manual/files/shcore.js.html&#34;&gt;SyntaxHighlighter&lt;/a&gt;.
This is a great library but loading it all the time when I just needed
it for certain posts was a bear. Instead, I&amp;rsquo;ll be using &lt;a href=&#34;https://gist.github.com/&#34;&gt;Github Gists&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id=&#34;things-im-still-tweaking&#34;&gt;Things I&amp;rsquo;m still tweaking&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Font. I wanted to avoid a web font due to the extra request,
but I&amp;rsquo;m not sure that&amp;rsquo;s possible. I&amp;rsquo;m just not happy with Arial/Verdana or Georgia on the Serif side&lt;/li&gt;
&lt;li&gt;Sizing. I increased the font size from the original &lt;a href=&#34;https://github.com/cdipaolo/gindoro&#34;&gt;gindoro&lt;/a&gt;
theme when tweaking my &lt;a href=&#34;https://github.com/elerch/gindoro&#34;&gt;fork&lt;/a&gt;. Since the point is
to have a blog, and the point of a blog is to read it, why not make the
size relatively large? Anyway, I&amp;rsquo;m still tweaking spacing and such as
a result.&lt;/li&gt;
&lt;li&gt;Flair. The site might be minimal, but maybe a bit too minimal.&lt;/li&gt;
&lt;li&gt;Comments. I&amp;rsquo;d like to enable comments without a lot of ceremony.
Currently I don&amp;rsquo;t have comments, but am considering &lt;a href=&#34;https://disqus.com/&#34;&gt;Disqus&lt;/a&gt; or &lt;a href=&#34;http://www.discourse.org&#34;&gt;Discourse&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;things-i-want-improved&#34;&gt;Things I want improved&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;d like for hugo to support explicit image sizing and relative urls
for images. On this post, I had to specify the full public url on the
image tags, and height/width directives do not pass through. Since hugo
is open source, that would be a great pull request in my spare time.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Probing support for architecture-specific native DLLs in .NET</title>
      <link>https://emil.lerch.org/2014-06-13-probing-support-for-architecture-specific-native-dlls-in-net/</link>
      <pubDate>Fri, 13 Jun 2014 13:50:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2014-06-13-probing-support-for-architecture-specific-native-dlls-in-net/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;./posts/2014-06-13/2014-06-13-probing-support-for-architecture-specific-native-dlls-in-net-2500121593_7ecf3c51a5_z.jpg&#34;&gt;&lt;img src=&#34;./posts/2014-06-13/thumbnails/2014-06-13-probing-support-for-architecture-specific-native-dlls-in-net-2500121593_7ecf3c51a5_z.jpg&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;orhow-ive-tamed-the-oracle-beast&#34;&gt;Or&amp;hellip;How I&amp;rsquo;ve tamed the Oracle beast&lt;/h2&gt;
&lt;p&gt;Over the last couple decades, very little has changed with regards to
Oracle client software. Install hundreds of MB of code, update
network/admin/tnsnames.ora, and finally you can make a database
connection.&lt;/p&gt;
&lt;p&gt;With a little (well, a lot) of elbow grease, I&amp;rsquo;ve made it possible for
.NET applications to run as &amp;ldquo;AnyCPU&amp;rdquo; and work in 32 or 64 bit process
space on either a 32 or 64 bit OS. It&amp;rsquo;s still an 84MB download (mostly
due to a large dll containing all of Oracle&amp;rsquo;s error messages), but it&amp;rsquo;s
bin-deployable, and only 20MB more than the combination of installing
each of Oracle&amp;rsquo;s &amp;ldquo;Instant Lite&amp;rdquo; processor-specific installation
packages. More importantly, since it&amp;rsquo;s bin-deployed, you can have 32 and
64 bit processes running side by side on the same machine, something
that&amp;rsquo;s been problematic to do with full client installs.&lt;/p&gt;
&lt;p&gt;The fruits of my labor are now on the the NativeProbing repository on
GitHub: &lt;a href=&#34;https://github.com/elerch/NativeProbing&#34;&gt;https://github.com/elerch/NativeProbing&lt;/a&gt;. The focus of the
repository is not on Oracle per-se, but my goal was to achieve XCopy
deployment for Oracle connectivity without massive installs or gnarly
configuration.&lt;/p&gt;
&lt;p&gt;.Net assemblies are loaded through the Fusion process, and typically
follow a strict path through an AnyCPU chain, 64 bit chain or 32 bit
chain. If using the &amp;lt;probing&amp;gt; element to alter flow and add
platform-specific assemblies, the first time .NET hits an assembly not
matching the underlying processor architecture you&amp;rsquo;ll get a
BadImageFormatException. This is especially nasty; running 32 bit on a
64 bit OS is not enough to see the exception, rather, you need to be 32
bit on a 32 bit OS with your 64 bit assemblies listed first in the
&amp;lt;probing&amp;gt; directory list. Therefore you need to hook into the
assembly resolution process itself similar to this
process: &lt;a href=&#34;http://stackoverflow.com/a/9951658/113225&#34;&gt;http://stackoverflow.com/a/9951658/113225&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;However, native libraries contain a special challenge. A different
Windows process kicks in when loading a native library, and in the case
of Oracle, the .Net assemblies used load native assemblies. My code on
GitHub therefore uses the SetDllDirectory kernel32.dll native function
to add the correct platform-specific directory to the list of paths to
check. The two techniques in concert work to automatically load the
appropriate DLLs.&lt;/p&gt;
&lt;p&gt;More details of my travels are on
&lt;a href=&#34;https://github.com/elerch/NativeProbing&#34;&gt;GitHub&lt;/a&gt;, but the bottom line
is that I have code that can augment the probing pipeline to include the
correct architecture specific DLLs. This can be used for any
architecture-specific DLLs with matching version numbers.
&lt;a href=&#34;https://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki&#34;&gt;SQLLite&lt;/a&gt; is
a possible candidate for using a similar technique.&lt;/p&gt;
&lt;p&gt;Of course, Oracle does &lt;strong&gt;not&lt;/strong&gt; have matching version numbers, which is
where part two of this story unfolds. To make Oracle.DataAcess.dll work,
I had to resort to extreme measures. The details are on the
&lt;a href=&#34;https://github.com/elerch/NativeProbing/blob/master/AnyCPU/OracleDLLHacking.md&#34;&gt;OracleDLLHacking.md&lt;/a&gt;
file in my repository, but the high level process was this: &lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Disassemble Oracle.DataAccess.dll with ildasm&lt;/li&gt;
&lt;li&gt;Remove the public key token and change the version number in the IL&lt;/li&gt;
&lt;li&gt;Reverse the branch logic where an exception is thrown if the
assembly is &amp;ldquo;incompatible&amp;rdquo; with the native code&lt;/li&gt;
&lt;li&gt;Reassemble the library with ilasm&lt;/li&gt;
&lt;li&gt;Repeat for the other processor architecture&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Visual Studio unit testing slow when NetBIOS over TCP/IP is enabled</title>
      <link>https://emil.lerch.org/2014-01-13-visual-studio-unit-testing-slow-when-netbios-over-tcp-ip-is-enabled/</link>
      <pubDate>Mon, 13 Jan 2014 19:01:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2014-01-13-visual-studio-unit-testing-slow-when-netbios-over-tcp-ip-is-enabled/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;http://connect.microsoft.com/VisualStudio/feedback/details/768230/slow-running-in-test-runner&#34;&gt;According to MS
Connect&lt;/a&gt;,
this doesn&amp;rsquo;t happen. People have reported it, however, in VS 2010, 2012,
and I&amp;rsquo;ve experienced the problems in VS 2013. It&amp;rsquo;s also listed at this
StackOverflow
question: &lt;a href=&#34;http://connect.microsoft.com/VisualStudio/feedback/details/768230/slow-running-in-test-runner&#34;&gt;http://connect.microsoft.com/VisualStudio/feedback/details/768230/slow-running-in-test-runner&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve now added this handy PowerShell command to my &amp;ldquo;initialize a new
machine&amp;rdquo; setup PowerShell script:&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;# Disable
NetBios over TCP/IP on all interfaces&lt;!-- raw HTML omitted --&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;# to
prevent weird Visual Studio slowdowns during unit tests&lt;!-- raw HTML omitted --&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;Get-ChildItem
hklm:system/currentcontrolset/services/netbt/parameters/interfaces
| foreach{ $item = $_; Set-ItemProperty -Path
($item.ToString().Replace(&amp;ldquo;HKEY_LOCAL_MACHINE&amp;rdquo;, &amp;ldquo;hklm:&amp;rdquo;)) -Name
NetbiosOptions -Type DWord -Value 2 -ea &amp;ldquo;SilentlyContinue&amp;rdquo; } &lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>REST Basics</title>
      <link>https://emil.lerch.org/2013-07-12-rest-basics/</link>
      <pubDate>Fri, 12 Jul 2013 14:41:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2013-07-12-rest-basics/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve created a video on the basic use of REST from a consuming
developer&amp;rsquo;s perspective.  It covers the following:&lt;/p&gt;
&lt;p&gt;Normal HTTP Methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;POST&lt;/li&gt;
&lt;li&gt;GET&lt;/li&gt;
&lt;li&gt;PUT&lt;/li&gt;
&lt;li&gt;DELETE&lt;/li&gt;
&lt;li&gt;PATCH&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I cover the use of PATCH to replace special-purpose functions in MVC,
singular vs plural urls and search apis.  I also demonstrate the use of
the methods at a browser console (w/jQuery). Length - 19:23.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>AngularJS and broken http options</title>
      <link>https://emil.lerch.org/2013-05-02-angularjs-and-broken-http-options/</link>
      <pubDate>Thu, 02 May 2013 18:19:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2013-05-02-angularjs-and-broken-http-options/</guid>
      <description>&lt;p&gt;Complicating the &lt;a href=&#34;http://emilsblog.lerch.org/2013/05/angularjs-html5-routing-and-ie9.html&#34;&gt;HTML5 routing and IE9
issue&lt;/a&gt;
was the fact that AngularJS looks fundamentally broken regarding
behavior around http settings.  Our server offers &lt;a href=&#34;https://en.wikipedia.org/wiki/Content_negotiation&#34;&gt;content
negotiation&lt;/a&gt;, so an
application url for /widgets requesting a content
&lt;a href=&#34;http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1&#34;&gt;Accept&lt;/a&gt;
header of text/html will result in the server sending HTML for the set
of widgets it knows about.  If, however, the Accept type header is set
to application/json, the server will respond with a json object with an
array property containing widget data.&lt;/p&gt;
&lt;p&gt;Our first problem is that a user browsing the site will want a full HTML
page while Angular provides no facility (that I&amp;rsquo;ve found) for specifying
an element in a full page to gather for the view.  It expects an HTML
fragment consisting of a single root element.  To tackle this problem,
we added an &amp;ldquo;X-Partial&amp;rdquo; header to let the server know to include only
the partial content.&lt;/p&gt;
&lt;p&gt;This worked fine and setting the $http settings is &lt;a href=&#34;http://docs.angularjs.org/api/ng.$http&#34;&gt;clearly
documented&lt;/a&gt;&amp;hellip;but also starts to
have a code smell.  The
&lt;a href=&#34;http://docs.angularjs.org/api/ng.$routeProvider&#34;&gt;$routeProvider&lt;/a&gt;
doesn&amp;rsquo;t seem to have a way to specify http settings, so the only way to
set headers is by setting http defaults.&lt;/p&gt;
&lt;p&gt;As it turns out, while they are named defaults, they are not, in fact,
defaults.  &lt;a href=&#34;https://github.com/angular/angular.js/blob/a348e90aa141921b914f87ec930cd6ebf481a446/src/ng/http.js#L655&#34;&gt;Deep in the code of
AngularJS&lt;/a&gt;,
you&amp;rsquo;ll see the following calls:&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>AngularJs HTML5 routing and IE9</title>
      <link>https://emil.lerch.org/2013-05-02-angularjs-html5-routing-and-ie9/</link>
      <pubDate>Thu, 02 May 2013 17:05:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2013-05-02-angularjs-html5-routing-and-ie9/</guid>
      <description>&lt;p&gt;We seem to be pushing the limits of &lt;a href=&#34;http://angularjs.org/&#34;&gt;AngularJS&lt;/a&gt;,
and we&amp;rsquo;ve only started using it. The framework is very promising but
definitely very new and a little rough in some areas.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://gs.statcounter.com/#browser_version_partially_combined-US-monthly-201205-201304&#34;&gt;&lt;img src=&#34;./posts/2013-05-02/2013-05-02-angularjs-html5-routing-and-ie9-StatCounter-browser_version_partially_combined-US-monthly-201205-201304.jpeg&#34; alt=&#34;&#34;&gt;&lt;/a&gt;We&amp;rsquo;d
like to &lt;a href=&#34;http://isolani.co.uk/blog/javascript/BreakingTheWebWithHashBangs&#34;&gt;avoid hash
urls&lt;/a&gt;
in our solutions. The backend can respond to our routes just fine, and
our backend framework is able to serve up the correct content (not
necessarily the same content) for any area of the application. Using
HTML5 mode and the &lt;a href=&#34;http://caniuse.com/#feat=history&#34;&gt;history.pushState&lt;/a&gt;
api, we can get clean URLs and no 404 errors on refresh&amp;hellip;awesome.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Enter, IE9 (top light blue line)&lt;/strong&gt;. Our current browser standards are
IE9+, Desktop/Android Chrome, FF, and IOS Safari. All browsers support
history.pushState except IE9. The browser has had a recent significant
drop in usage, but we can&amp;rsquo;t ignore it quite yet.&lt;/p&gt;
&lt;p&gt;At first, we thought we could set $locationProvider.html5Mode(true) and
it would either a) break in IE9 calling a function that didn&amp;rsquo;t exist
(but we could define as window.location.assign(url)), or b) do a
browser-based redirect based on
&lt;a href=&#34;https://developer.mozilla.org/en-US/docs/DOM/window.location&#34;&gt;window.location.assign&lt;/a&gt;.
 Well,
&lt;a href=&#34;http://docs.angularjs.org/guide/dev_guide.services.$location&#34;&gt;RTFM&lt;/a&gt;:
 what it does is actually relatively painful.  It falls back to hashbang
syntax.  This is similar to
&lt;a href=&#34;http://balupton.github.io/history.js/demo/&#34;&gt;history.js&lt;/a&gt;, and IMHO, is
broken in the same way as that library.  Now you have URLs you can&amp;rsquo;t
share between browsers without crazy (and slow) workarounds.&lt;/p&gt;
&lt;p&gt;I tried to work around the problem by providing a history.pushState
implementation.  This fooled Angular into thinking it was compliant, but
caused some redirect loops and ultimately didn&amp;rsquo;t work out.  Next, I
tried first intercepting routeChangeStart and &lt;a href=&#34;https://github.com/angular/angular.js/issues/2109&#34;&gt;calling
event.preventDefault to no
avail&lt;/a&gt;, then
locationChangeStart also without success (I&amp;rsquo;m informed &lt;a href=&#34;http://stackoverflow.com/a/13963919/113225&#34;&gt;this does
work&lt;/a&gt;, but for some reason I
didn&amp;rsquo;t see it, at least not in IE9).&lt;/p&gt;
&lt;p&gt;Finally, it hit me&amp;hellip;since we&amp;rsquo;re not programmatically changing locations
(just using &lt;a href=&#34;http://docs.angularjs.org/api/ng.directive:a&#34;&gt;anchor
tags&lt;/a&gt;), we could simply
determine if &lt;a href=&#34;http://stackoverflow.com/a/10647429/113225&#34;&gt;pushState is
available&lt;/a&gt; and perform the
following functions if it is not:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Avoid defining any routes with the route provider&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Take our &lt;a href=&#34;http://stackoverflow.com/a/10647429/113225&#34;&gt;&amp;lt;script id=&amp;lsquo;whateverroutewearecurrentlyon&amp;rsquo;
type=&amp;lsquo;text/ng-template&amp;rsquo;/&amp;gt;&lt;/a&gt;
that is automatically generated on the backend for the current URL
and manually add it to the ng-view element.&lt;/li&gt;
&lt;li&gt;Remove the ng-view attribute (for completeness) and add an
ng-controller attribute pointing to the controller for the current
route.&lt;/li&gt;
&lt;li&gt;Disable any pre-loading for pages other than the one on which we
reside.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This technique can only work in a pretty specific set of circumstances,
but I think a properly designed backend system should be able to meet
the criteria:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Any URL used by history.pushState actually generates appropriate
content from the server.&lt;/li&gt;
&lt;li&gt;The server at any URL provides the page layout and the application
code.  Providing the current page content in the script tag is nice
(that&amp;rsquo;s what we do) but it&amp;rsquo;s not strictly required (#2 above could
be done via Ajax).&lt;/li&gt;
&lt;li&gt;The application code is light enough that you (and your users) can
put up with full page loads in non-compliant browsers until users
upgrade.  I wouldn&amp;rsquo;t want to try this technique on gmail. ;-)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I &lt;a href=&#34;http://www.hanselman.com/blog/IdLikeToUseTheWebMyWayThankYouVeryMuchQuora.aspx&#34;&gt;reject Quora on
principal&lt;/a&gt;,
but at least now we have an answer to &lt;a href=&#34;http://www.quora.com/AngularJS/Can-we-develop-an-AngularJS-application-which-supports-IE-7+-but-does-not-have-hash-or-hashbang-urls&#34;&gt;&amp;ldquo;Can we develop an AngularJS
application which supports IE 7+ but does not have hash or hashbang
urls?&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Enabling Quartz jobs in ASP.NET applications that will run despite restart</title>
      <link>https://emil.lerch.org/2013-01-11-enabling-quartz-jobs-in-asp-net-applications-that-will-run-despite-restart/</link>
      <pubDate>Fri, 11 Jan 2013 16:29:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2013-01-11-enabling-quartz-jobs-in-asp-net-applications-that-will-run-despite-restart/</guid>
      <description>&lt;p&gt;With IIS 7.5, you can now &lt;a href=&#34;http://weblogs.asp.net/scottgu/archive/2009/09/15/auto-start-asp-net-applications-vs-2010-and-net-4-0-series.aspx&#34;&gt;auto-start applications and have them
continuously
run&lt;/a&gt;.
 However,
implementing &lt;a href=&#34;http://msdn.microsoft.com/en-us/library/system.web.hosting.iprocesshostpreloadclient.aspx&#34;&gt;System.Web.Hosting.IProcessHostPreloadClient&lt;/a&gt;
means having a Process method with some significant restrictions.  In a
&lt;a href=&#34;http://www.springframework.net/&#34;&gt;Spring.Net&lt;/a&gt; environment, the IOC
Container&amp;rsquo;s context is not yet started, and it will fail in &lt;a href=&#34;http://stackoverflow.com/questions/13162545/handler-extensionlessurlhandler-integrated-4-0-has-a-bad-module-managedpipeli&#34;&gt;rather
spectacular
ways&lt;/a&gt;
if you try to crank it up with a hack.  Even if you manage to do this,
Quartz does not start up, so your jobs still will fail to run.&lt;/p&gt;
&lt;p&gt;I spent some time on this problem and discovered that I do have access
to &lt;a href=&#34;http://msdn.microsoft.com/en-us/library/system.web.httpruntime.appdomainappvirtualpath.aspx&#34;&gt;System.Web.HttpRuntime.AppDomainAppVirtualPath&lt;/a&gt;,
which allows us to automatically fire an initial request at the app
automatically, if we&amp;rsquo;re careful.  This will crank up Application_Start
and the rest of the Spring (and Quartz) machinery, allowing us to keep
an app running 100% of the time.  This initial request, however, will be
thrown away, even if fired asynchronously, if initiated during the
Preload() method.  I ended up using a thread to get around this problem,
allowing the requests to fire 500ms after the Preload method runs.&lt;/p&gt;
&lt;p&gt;In certain cases, we can&amp;rsquo;t just use
http://localhost/&lt;em&gt;yourvirtualpathhere&lt;/em&gt;.  You might have multiple sites
listening to different host headers, or you might be running SSL and
don&amp;rsquo;t want to hit the application using localhost.  To cover those
cases, I devised a web.config appSettings scheme where additional URLs
can be applied to Preload (space delimited).  You can also specify an IP
address or machine name, so the web.config can stay static but different
URLs can be applied as you move through environments.  Here is the
code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Preloader : System.Web.Hosting.IProcessHostPreloadClient
    {
        public void Preload(string[] parameters)
        {
            var uris = System.Configuration.ConfigurationManager.AppSettings[&amp;quot;AdditionalStartupUris&amp;quot;];
            StartupApplication(AllUris(uris));
        }

        public void StartupApplication(IEnumerable&amp;lt;Uri&amp;gt; uris)
        {
            new System.Threading.Thread(o =&amp;gt;
            {
                System.Threading.Thread.Sleep(500);
                foreach (var uri in (IEnumerable&amp;lt;Uri&amp;gt;)o) {
                    var client = new System.Net.WebClient();
                    client.DownloadStringAsync(uris.First());
                }
            }).Start(uris);
        }

        public IEnumerable&amp;lt;Uri&amp;gt; AllUris(string userConfiguration)
        {
            if (userConfiguration == null)
                return GuessedUris();
            return AllUris(userConfiguration.Split(&#39; &#39;)).Union(GuessedUris());
        }

        private IEnumerable&amp;lt;Uri&amp;gt; GuessedUris()
        {
            string path = System.Web.HttpRuntime.AppDomainAppVirtualPath;
            if (path != null)
                yield return new Uri(&amp;quot;http://localhost&amp;quot; + path);
        }

        private IEnumerable&amp;lt;uri&amp;gt; AllUris(params string[] configurationParts)
        {
            return configurationParts
                .Select(p =&amp;gt; ParseConfiguration(p))
                .Where(p =&amp;gt; p.Item1)
                .Select(p =&amp;gt; ToUri(p.Item2))
                .Where(u =&amp;gt; u != null);
        }

        private Uri ToUri(string value)
        {
            try {
                return new Uri(value);
            }
            catch (UriFormatException) {
                return null;
            }
        }

        private Tuple&amp;lt;bool string&amp;gt; ParseConfiguration(string part)
        {
            return new Tuple&amp;lt;bool, string&amp;gt;(IsRelevant(part), ParsePart(part));
        }

        private string ParsePart(string part)
        {
            // We expect IPv4 or MachineName followed by |
            var portions = part.Split(&#39;|&#39;);
            return portions.Last();
        }

        private bool IsRelevant(string part)
        {
            var portions = part.Split(&#39;|&#39;);
            return
                portions.Count() == 1 ||
                portions[0] == System.Environment.MachineName ||
                HostIpAddresses().Any(a =&amp;gt; a == portions[0]);
        }

        private IEnumerable&amp;lt;string&amp;gt; HostIpAddresses()
        {
            var adaptors = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();
            return adaptors
                    .Where(a =&amp;gt; a.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up)
                    .SelectMany(a =&amp;gt; a.GetIPProperties().UnicastAddresses)
                    .Where(a =&amp;gt; a.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    .Select(a =&amp;gt; a.Address.ToString());
        }
    }
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>Installing Cyanogenmod 7.2 on Verizon Droid 2 Running 2.3.4</title>
      <link>https://emil.lerch.org/2012-12-14-installing-cyanogenmod-7-2-on-verizon-droid-2-running-2-3-4/</link>
      <pubDate>Fri, 14 Dec 2012 10:48:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2012-12-14-installing-cyanogenmod-7-2-on-verizon-droid-2-running-2-3-4/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;http://wiki.cyanogenmod.org/wiki/Motorola_Droid_2:_Full_Update_Guide&#34;&gt;They said it couldn&amp;rsquo;t be
done&lt;/a&gt;.
 If you install 2.3.4 OTA update from Verizon, you were stuck with a
version that was un-rootable.  And rooting is the first step in getting
a custom Android build on a phone.  Specifically:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There is currently (as of May 2012) no way to root a Droid 2 with this
system version.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a word of warning, the procedures for getting Cyanodenmod are as
scary as the link above, and with 2.3.4, you&amp;rsquo;re really in the deep end.
 Here&amp;rsquo;s a summary of what I did.  I&amp;rsquo;m not providing step-by-step
directions for two reasons; first, if you can&amp;rsquo;t get there from the
summary, you&amp;rsquo;re probably in over your head and you have a good chance of
bricking your phone.  Secondly, I am providing links to my source
materials with more details.  I&amp;rsquo;m also taking you through the method I
went, which generally used tools rather than adb commands/doing it by
hand.  I purchased two of the tools&amp;hellip;I think the $8 I spent was worth
it, but go read the wiki if you want to do it the hard way.&lt;/p&gt;
&lt;h3 id=&#34;rooting&#34;&gt;Rooting&lt;/h3&gt;
&lt;p&gt;The rooting procedures on the Cyanogenmod wiki don&amp;rsquo;t work, obviously.
 What you need to do is download this
iso: &lt;a href=&#34;http://www.mediafire.com/?mim304k214kl41h&#34;&gt;http://www.mediafire.com/?mim304k214kl41h&lt;/a&gt;.  MD5
is 7baee9c34f6ef7ad0b4fa219ae387c68.  The forum post regarding this is
here: &lt;a href=&#34;http://androidforums.com/droid-x-all-things-root/603489-how-root-2-3-4-4-5-621-magic-md5-does-not-require-milestone-sbf.html&#34;&gt;http://androidforums.com/droid-x-all-things-root/603489-how-root-2-3-4-4-5-621-magic-md5-does-not-require-milestone-sbf.html&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The ISO can be burned to a bootable CD or USB (using
&lt;a href=&#34;http://unetbootin.sourceforge.net/unetbootin-windows-latest.exe&#34;&gt;Unetbutin&lt;/a&gt;).
 I went the USB route.  Once booting off the USB, the scripts on the ISO
walk you through what needs to be done.  This was by far the easiest
part of the install.&lt;/p&gt;
&lt;h3 id=&#34;custom-recovery-rom-and-bootloader&#34;&gt;Custom Recovery ROM and Bootloader&lt;/h3&gt;
&lt;p&gt;Not having gone down these paths before, I didn&amp;rsquo;t have a conceptual
understanding of how Android boot works.  That turns out to be pretty
important at this point, and it tripped me up for a bit.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Bootloader is run, presumably by the hardware&lt;/li&gt;
&lt;li&gt;Bootloader looks around, checks out everything, and loads the ROM
image.  Depending on the environment it can run the recovery ROM or
the normal boot ROM (your phone).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So, now that the phone is rooted, we need a custom bootloader because
the Android one will verify that it&amp;rsquo;s the Verizon image, and we don&amp;rsquo;t
want that.  We need a custom recovery ROM as well&amp;hellip;one that will let us
flash new Android versions.  So, the next step is to install the Droid 2
Recovery Bootstrap by
ClockworkMod: &lt;a href=&#34;http://market.android.com/details?id=com.koushikdutta.droid2.bootstrap&#34;&gt;http://market.android.com/details?id=com.koushikdutta.droid2.bootstrap&lt;/a&gt;.
 Also install the ROM
Manager: &lt;a href=&#34;https://play.google.com/store/apps/details?id=com.koushikdutta.rommanager.license&amp;amp;feature=more_from_developer#?t=W251bGwsMSwxLDEwMiwiY29tLmtvdXNoaWtkdXR0YS5yb21tYW5hZ2VyLmxpY2Vuc2UiXQ..&#34;&gt;https://play.google.com/store/apps/details?id=com.koushikdutta.rommanager.license&amp;amp;feature=more_from_developer#?t=W251bGwsMSwxLDEwMiwiY29tLmtvdXNoaWtkdXR0YS5yb21tYW5hZ2VyLmxpY2Vuc2UiXQ..&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Use the ROM Manager to Flash ClockworkMod Recovery.  Once that&amp;rsquo;s done,
install the Recovery Bootstrap (remember, we need the Bootstrap to let
us get into our shiny new Recovery).  I missed this part and I kept
going into the Android recovery&amp;hellip;it was very confusing.&lt;/p&gt;
&lt;h3 id=&#34;prepping-and-hacking-the-cyanogen-install&#34;&gt;Prepping and Hacking the Cyanogen install&lt;/h3&gt;
&lt;p&gt;Download:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Cyanogen latest
version: &lt;a href=&#34;http://wiki.cyanogenmod.org/wiki/Devices_Overview#Motorola_Droid_2&#34;&gt;http://wiki.cyanogenmod.org/wiki/Devices_Overview#Motorola_Droid_2&lt;/a&gt;.
 Right now, this is 7.2.0, available
at: &lt;a href=&#34;http://get.cm/get/jenkins/2824/cm-7.2.0-droid2.zip&#34;&gt;http://get.cm/get/jenkins/2824/cm-7.2.0-droid2.zip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Google apps (gets you the
market): &lt;a href=&#34;http://goo-inside.me/gapps/gapps-gb-20110828-signed.zip&#34;&gt;http://goo-inside.me/gapps/gapps-gb-20110828-signed.zip&lt;/a&gt;.
 That is the mirror link as the primary didn&amp;rsquo;t work for me.  The
cyanogenmod page on this is
at: &lt;a href=&#34;http://wiki.cyanogenmod.org/wiki/Latest_Version#Google_Apps&#34;&gt;http://wiki.cyanogenmod.org/wiki/Latest_Version#Google_Apps&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Copy the Google apps zip file to the SD card.  I don&amp;rsquo;t know if this
needs to be in the root directory, but Cyanogen instructions mention
putting it into root.  It&amp;rsquo;s certainly easier, anyway.&lt;/p&gt;
&lt;p&gt;**Don&amp;rsquo;t copy the Cyanogen zip file to the SD card yet.  **Our last step
in this path is that there is a safety check in the install to verify
the kernel version so it doesn&amp;rsquo;t accidentally brick your phone.  With
2.3.4 we&amp;rsquo;re safe, but Cyanogen doesn&amp;rsquo;t know this.  So, you need to unzip
the Cyanogen zip file above, and alter the /system/etc/check_kernel
script (with a decent text editor - not notepad).  Wipe out everything
except the first line.  Then the second line should be simply &amp;ldquo;exit 0&amp;rdquo;.
 More details are
here: &lt;a href=&#34;http://forum.cyanogenmod.org/topic/50469-installing-cm-from-234/#entry335479&#34;&gt;http://forum.cyanogenmod.org/topic/50469-installing-cm-from-234/#entry335479&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Zip the file with the changed script and put it on your SD card from the
phone.  &lt;/p&gt;
&lt;h3 id=&#34;installing-cyanogen&#34;&gt;Installing Cyanogen&lt;/h3&gt;
&lt;p&gt;Reboot the phone into ClockworkMod recovery by first powering down.
 Then hold power and the X key on the keyboard until it comes up.
 You&amp;rsquo;ll see ClockworkMod come up (maybe).  I remember having an issue
where nothing seems to happen.  If you have that problem, hit both
volume keys at the same time (if that fails, just play around with
volume a bit).  You&amp;rsquo;ll then see shiny new text from ClockworkMod
recovery, and maybe even an error message.  &lt;/p&gt;
&lt;p&gt;At this point, it took me a while to figure out, and the ROM manager was
useless for me.  The directions and forums don&amp;rsquo;t know a whole lot about
that error message.  You&amp;rsquo;re &lt;strong&gt;supposed&lt;/strong&gt; to be able to use up/down
volume to arrow around the options, and power selects the option you
want.  However, power just made the phone go blank for me.  I
&lt;strong&gt;think&lt;/strong&gt; what&amp;rsquo;s happening is that the droid 2 does not have /dev/tty0
and there is a bug in the recovery ROM where the power button selects
/dev/tty0 as the device when pressed.  However, there is another way to
navigate&amp;hellip;&lt;/p&gt;
&lt;p&gt;What I &lt;strong&gt;finally figured out&lt;/strong&gt; is that navigation can operate with the
keyboard.  Slide it open, and left = up, right = down (which makes sense
since you have the phone in portrait mode.  Up = right, Enter = enter,
and Del = back.  This knows where the screen is, and I&amp;rsquo;m sure it&amp;rsquo;s much
more pleasant than trying to navigate around selecting files with volume
keys/power.&lt;/p&gt;
&lt;p&gt;With that working, you can perform the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Back up your existing ROM&lt;/li&gt;
&lt;li&gt;Select and install the Cyanogen ROM&lt;/li&gt;
&lt;li&gt;Select and install the Google Apps ROM (I think this just unpacks
apks and puts them in a special directory, but I&amp;rsquo;m not sure).  I
thought it was way weird I was installing two ROMs.&lt;/li&gt;
&lt;li&gt;Lastly, wipe all cache/data.  I don&amp;rsquo;t remember if I performed
factory reset - I think I did &lt;strong&gt;not&lt;/strong&gt;.  Apparently the phone will
get into a &amp;ldquo;boot loop&amp;rdquo; unless this step is performed, but if you
forget for some reason, you can get out of that
problem: &lt;a href=&#34;http://forum.xda-developers.com/showthread.php?t=1832130&#34;&gt;http://forum.xda-developers.com/showthread.php?t=1832130&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Congrats, Cyanogen is installed!  It&amp;rsquo;s like a brand new phone, so you&amp;rsquo;ll
have to sign in, install apps, yada yada.&lt;/p&gt;
&lt;h3 id=&#34;other-thoughts&#34;&gt;Other thoughts&lt;/h3&gt;
&lt;p&gt;I noticed the Alt Lock button does not work after I installed.
 Apparently this is a special Motorola hardware key and cannot be used
by Cyanogen.  However, pressing Alt twice will do the same thing&amp;hellip;I&amp;rsquo;m
sure that&amp;rsquo;s a default Android behavior I had no idea about.  I am also
missing the Motorola calendar widget terribly, but I&amp;rsquo;m sure I&amp;rsquo;ll find a
suitable replacement.  Cyanogen is so much better/faster.  They really
had me at &amp;ldquo;Add Access Point&amp;rdquo; in the wireless settings. ;-)&lt;/p&gt;
&lt;h4 id=&#34;update&#34;&gt;Update&lt;/h4&gt;
&lt;p&gt;I noticed that my WiFi does not seem to connect. It connects but the
advanced settings says IP address unavailable. I tried a custom kernel
but to no avail. It appears to be a dhcp problem only, and I think it is
a problem with cm 7.2. I have worked around the problem by using a
static IP address and DNS server, but the static DNS settings in the
settings don&amp;rsquo;t seem to work. Instead I installed &lt;a href=&#34;http://www.google.com/url?sa=t&amp;amp;rct=j&amp;amp;q=set%20dns%20android&amp;amp;source=web&amp;amp;cd=1&amp;amp;cad=rja&amp;amp;ved=0CDIQFjAA&amp;amp;url=https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Duk.co.mytechie.setDNS%26hl%3Den&amp;amp;ei=o8rgULfkEIyyqAHRvoDgAg&amp;amp;usg=AFQjCNH1ntGZvyswfVMGi7p1o2f_NMzBYg&amp;amp;sig2=v3R48xbCfBSQRE7lheDDuw&amp;amp;bvm=bv.1355534169,d.aWM&#34;&gt;Set
DNS&lt;/a&gt; and
that works properly. For WiFi hotspot support I installed &lt;a href=&#34;http://www.google.com/url?sa=t&amp;amp;rct=j&amp;amp;q=barnacle%20wifi%20tether&amp;amp;source=web&amp;amp;cd=1&amp;amp;cad=rja&amp;amp;ved=0CDIQFjAA&amp;amp;url=https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Dnet.szym.barnacle%26hl%3Den&amp;amp;ei=W8vgULidF8uLrQG2moDgDw&amp;amp;usg=AFQjCNEp_MFCsxl_uO7sdCVHLVCrsQXnxA&amp;amp;sig2=OUj3wib0TEHOEH-BHfaSgw&amp;amp;bvm=bv.1355534169,d.aWM&#34;&gt;Barnacle WiFi
tether&lt;/a&gt;,
but I haven&amp;rsquo;t had a chance to play with it too much.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The constraint for route parameter &#39;httpMethod&#39; on the route with URL &#39;{controller}/{id}&#39; must have a string value in order to use an HttpMethodConstraint.</title>
      <link>https://emil.lerch.org/2012-05-24-the-constraint-for-route-parameter-httpmethod-on-the-route-with-url-controller-id-must-have-a-string-value-in-order-to-use-an-httpmethodconstraint/</link>
      <pubDate>Thu, 24 May 2012 07:39:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2012-05-24-the-constraint-for-route-parameter-httpmethod-on-the-route-with-url-controller-id-must-have-a-string-value-in-order-to-use-an-httpmethodconstraint/</guid>
      <description>&lt;p&gt;Wow&amp;hellip;what did that mean?  Here I am, just using @Url.Action (and
BeginForm and any other method that walks the route table backwards).
 My application is RESTful, so most routes have an HttpMethodConstraint
so they only match if the method is correct.&lt;/p&gt;
&lt;p&gt;I could not wrap my head around this message and ended up using
&lt;a href=&#34;http://www.telerik.com/products/decompiler.aspx&#34;&gt;JustDecompile&lt;/a&gt; to pull
open System.Web.Routing and had a look at
the &lt;a href=&#34;http://msdn.microsoft.com/en-us/library/system.web.routing.httpmethodconstraint&#34;&gt;HttpMethodConstraint&lt;/a&gt; &lt;a href=&#34;http://msdn.microsoft.com/en-us/library/system.web.routing.httpmethodconstraint.match&#34;&gt;Match&lt;/a&gt; method.
 You&amp;rsquo;ll note that
the &lt;a href=&#34;http://msdn.microsoft.com/en-us/library/system.web.routing.routedirection&#34;&gt;routeDirection&lt;/a&gt; parameter
tells the object whether to match based on an incoming request (the
normal case) or for Url Generation (used for Url.Action, BeginForm and
the like).&lt;/p&gt;
&lt;p&gt;Pulling open the source, I found this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    switch (routeDirection1)
    {
        case RouteDirection.IncomingRequest:
        {
            ICollection&amp;lt;string&amp;gt; allowedMethods = this.AllowedMethods;
            if (func == null)
            {
                func = (string method) =&amp;gt; string.Equals(method, httpContext.Request.HttpMethod, StringComparison.OrdinalIgnoreCase);
            }
            return allowedMethods.Any&amp;lt;string&amp;gt;(func);
        }
        case RouteDirection.UrlGeneration:
        {
            if (values.TryGetValue(parameterName, out obj))
            {
                string str = obj as string;
                if (str != null)
                {
                    return this.AllowedMethods.Any&amp;lt;string&amp;gt;((string method) =&amp;gt; string.Equals(method, str, StringComparison.OrdinalIgnoreCase));
                }
                else
                {
                    object[] url = new object[2];
                    url[0] = parameterName;
                    url[1] = route.Url;
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.HttpMethodConstraint_ParameterValueMustBeString, url));
                }
            }
            else
            {
                return true;
            }
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The RouteDirection.UrlGeneration case block represents what happens when
the HttpMethodConstraint is asked to match for a generated Url.  The
InvalidOperationException represents the error message we see on the
YSOD.&lt;/p&gt;
&lt;p&gt;Even this took me a bit to work through, but the bottom line is that
it&amp;rsquo;s finding the parameter name from the route and looking for the value
of that parameter name from the input provided to it.  Given the
following route:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    routes.MapRoute(&amp;quot;UpdateByPut&amp;quot;,
        &amp;quot;{controller}/{id}&amp;quot;,
        new { action = &amp;quot;Update&amp;quot; },
        new { httpMethod = new HttpMethodConstraint(&amp;quot;PUT&amp;quot;) }
    );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HttpMethodConstraint will find httpMethod (this is the value of
parameterName in the code above), and look for a route value of
httpMethod, expecting it to be a string.  If it doesn&amp;rsquo;t, it will throw
the exception.  So, using the route above with the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  @Url.Action(&amp;quot;Show&amp;quot;, &amp;quot;MyEntity&amp;quot;, new { id = Model.Id })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Won&amp;rsquo;t work.  Instead, you need this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  @Url.Action(&amp;quot;Show&amp;quot;, &amp;quot;MyEntity&amp;quot;, new { id = Model.Id, httpMethod = &amp;quot;GET&amp;quot; })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also note that the property name httpMethod in the @Url.Action line
above comes from the same property name I used in routes.MapRoute within
Global.asax.cs.  This is one of the few times that MVC cares about the
property name used on a constraint.  &lt;/p&gt;
&lt;p&gt;Once you understand what the error message is trying to tell you it&amp;rsquo;s
all pretty clear.  You just need to be a rocket scientist the first time
through.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Developing to Paypal</title>
      <link>https://emil.lerch.org/2011-06-29-developing-to-paypal/</link>
      <pubDate>Wed, 29 Jun 2011 07:04:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2011-06-29-developing-to-paypal/</guid>
      <description>&lt;p&gt;PDT&lt;br&gt;
IPN&lt;br&gt;
Paypal APIs:&lt;/p&gt;
&lt;p&gt;developer.paypal.com&lt;br&gt;
sandbox.paypal.com&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>EduSpring Part 6: Crazy Error Messages and What to do about them</title>
      <link>https://emil.lerch.org/2011-06-14-eduspring-part-6-crazy-error-messages-and-what-to-do-about-them/</link>
      <pubDate>Tue, 14 Jun 2011 09:36:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2011-06-14-eduspring-part-6-crazy-error-messages-and-what-to-do-about-them/</guid>
      <description>&lt;p&gt;Because Spring will create most of your objects up front, a simple error
in the configuration Xml can have disastrous effects.  This, in my
opinion, is the #1 reason people fear Spring.&lt;/p&gt;
&lt;p&gt;Here are a few error messages I&amp;rsquo;ve seen, and their corresponding
solutions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The virtual path &amp;lsquo;/currentcontext.dummy&amp;rsquo; maps to another
application, which is not allowed&lt;/strong&gt;: This error message usually
means you&amp;rsquo;ve deployed the application to a server, but forgotten to
make the virtual directory an application in IIS.  Spring performs a
server.Transfer() call to &amp;ldquo;~/currentcontext.dummy&amp;rdquo; to get all the
dependency wiring done correctly before another server.Transfer()
brings the request back to the right place.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;no application context for virtual path&lt;/strong&gt;:  This error message
tells you that there&amp;rsquo;s no spring configuration setup at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource handler for the &amp;lsquo;web&amp;rsquo; protocol is not defined&lt;/strong&gt;:  This
message tells you that the httpModule is not active.  Add the
spring.net HttpModule into the appropriate section of web.config
(system.web for IIS6, system.webserver for IIS7).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Could not load type from assembly&lt;/strong&gt;:  This may or may not have
anything to do with Spring, so take a look a the stack trace.  If
you see Spring.Core (specifically
Spring.Core.TypeResolution.TypeResolver.Resolve) in the trace, you
can be fairly certain you misspelled a type name in the
configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;node cannot be resolved for the specified context&lt;/strong&gt;: Spring tried
to assign an object or value to a property, but the property doesn&amp;rsquo;t
exist on the object.  It&amp;rsquo;s likely you misspelled the property name,
or maybe you refactored, removed the property, but forgot to update
the spring configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Things to check if your dependency is showing up as null:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is your configuration binding the right type?  For example, if your
property is a string, and you&amp;rsquo;re assigning a stringbuilder to it,
Spring will just ignore the assignment.&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re missing the object definition in the spring configuration.&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re missing the property definition in the spring configuration.&lt;/li&gt;
&lt;li&gt;Did you commit the cardinal sin of DI?  Do not use new MyType()!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Take a look through
&lt;a href=&#34;https://github.com/elerch/eduSpring/blob/master/4%20-%20Debugging%20Web%20Applications/Web.config&#34;&gt;web.config&lt;/a&gt;
and
&lt;a href=&#34;https://github.com/elerch/eduSpring/blob/master/4%20-%20Debugging%20Web%20Applications/spring.config&#34;&gt;spring.config&lt;/a&gt;
of the &lt;a href=&#34;https://github.com/elerch/eduSpring/tree/master/4%20-%20Debugging%20Web%20Applications&#34;&gt;Debugging Web
Applications&lt;/a&gt;
project in the &lt;a href=&#34;https://github.com/elerch/eduSpring&#34;&gt;eduSpring&lt;/a&gt; project
on GitHub for troubleshooting examples.  There are several errors
scattered throughout the two configuration files, and the errors are
fully documented.&lt;/p&gt;
&lt;p&gt;Also note that the best way to work through the troubleshooting
procedure is to correct each issue in turn, shut down the integrated web
server and F5 to view the next error.&lt;/p&gt;
&lt;p&gt;Setting up Spring.Net&amp;rsquo;s verbose logging is also shown in the web.config.
 You might notice that the actual project references do not include
anything except standard ASP.NET DLLs.  Spring, Common Logging, and
Log4Net must exist in the bin directory at run time, but no project
references are actually required.  With VS 2010 SP1 or higher, we can
put these semi-dependent DLLs in a special folder called
_bin_deployableAssemblies and the build procedure will copy them in
place.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>EduSpring Part 5: Spring.Net in an ASP.NET environment (including MVC)</title>
      <link>https://emil.lerch.org/2011-06-14-eduspring-part-5-spring-net-in-an-asp-net-environment-including-mvc/</link>
      <pubDate>Tue, 14 Jun 2011 07:49:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2011-06-14-eduspring-part-5-spring-net-in-an-asp-net-environment-including-mvc/</guid>
      <description>&lt;p&gt;This post is part of a
&lt;a href=&#34;http://emilsblog.lerch.org/search/label/EduSpring&#34;&gt;series&lt;/a&gt; on
Spring.NET. I recommend starting at the beginning if you haven&amp;rsquo;t
already.  Also, I am walking through code in the &lt;a href=&#34;https://github.com/elerch/eduSpring&#34;&gt;accompanying GitHub
project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;By now, you should have the basics of DI, IoC, and the benefits and
drawbacks of the approach.  Now, I&amp;rsquo;ll introduce you to the architecture
of Spring.NET in an ASP.NET environment.  I&amp;rsquo;m sure a lot of other IoC
frameworks operate in a similar manner.  If not, you can add code to
make them work that way. ;-)&lt;/p&gt;
&lt;p&gt;If you look at the
&lt;a href=&#34;https://github.com/elerch/eduSpring/tree/master/2%20-%20IocWithoutSpring&#34;&gt;IocWithoutSpring&lt;/a&gt;
project, you&amp;rsquo;ll see this Main function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void Main(string[] args)
        {
            //These two lines are handled by the Spring.NET HttpModule
            var container = new IoCContainer();
            container.Initialize();

            // These two lines are also handled by the HttpModule by a special 
            // syntax in the spring configuration
            var service = new DoSomeWork();
            service.Worker = container.GetObject(&amp;quot;myObject&amp;quot;);

            // This is what the ASP.NET Framework would do
            Console.WriteLine(&amp;quot;The output is: &amp;quot; + service.DoTheWork());
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most of the time, creation of the IoC container itself is a single
dependency that&amp;rsquo;s particularly hard to get rid of without writing some
reflection-style glue code.  Ideally, we want our objects to be
completely ignorant of this container, though.  In ASP.NET, the
web.config provides us with the concept of an HttpModule, which will
look at every request coming from into the web server and have an
opportunity to do something with it.  Taking advantage of this feature,
the Spring.Net team wrote an ASP.NET HttpModule that will do just that,
so the first two lines of main (instantiation and initialization of the
container) are handled by the ASP.NET framework.  Awesome!&lt;/p&gt;
&lt;p&gt;Now our Spring.Net dictionary is populated, assuming we have a valid
configuration.  I&amp;rsquo;ll address more about the pain points of Spring
configuration later, but this very early creation of lots of objects is
one of the main frustrations of people who want to use Spring.Net.  The
next question is, what about setting up dependencies in ASPX pages?&lt;/p&gt;
&lt;p&gt;Technically speaking, the ASP.NET framework parses an ASPX file (or MVC
view) and code-generates a class.  On each request, it creates an
instance of this class and allows it to process the request, before
destroying the object.  Very stateless, but this creation and
destruction of classes rubs against IoC&amp;rsquo;s
Dictionary&amp;lt;string,object&amp;gt; heart.&lt;/p&gt;
&lt;p&gt;If you &lt;a href=&#34;http://blogs.msdn.com/b/oldnewthing/archive/2008/05/28/8555658.aspx&#34;&gt;read this contract from the other
side&lt;/a&gt;
(a.k.a. how would I solve this problem if I were writing Spring.Net),
you can imagine yourself writing
a &lt;a href=&#34;http://msdn.microsoft.com/en-us/library/system.web.ui.pagehandlerfactory.aspx&#34;&gt;PageHandlerFactory&lt;/a&gt; that can
deliver the aspx page class with dependencies already injected.  There
are two problems you have to solve, however:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What name do you use to look up the object?&lt;/li&gt;
&lt;li&gt;How do you deal with request-specific data?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The answer to #1 is relatively obvious if you look at the problem from
the perspective of Spring.  The object doesn&amp;rsquo;t really have a name, so we
leave that blank, and the &lt;a href=&#34;http://msdn.microsoft.com/en-us/library/ms178116.aspx&#34;&gt;virtual
path&lt;/a&gt; (~/page.aspx)
is used as the type of the object since someone configuring the object
will not know the type ASP.NET generates.  The actual type name needs to
be figured out by Spring.  The answer to the second question runs a bit
contrary to what I&amp;rsquo;ve been discussing so far.&lt;/p&gt;
&lt;p&gt;In the examples so far, the initialization method of the IoC container
has created all the objects in configuration and put them in its
internal dictionary.  However, there&amp;rsquo;s no reason that the objects need
to be created right away, nor do they have to be held in the dictionary.
 In the case of this PageHandlerFactory, objects are created and
configured at the time of the request, not at initialization.  However,
dependencies follow the normal rules.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>EduSpring Part 4: What is so terribly broken with Dependency Injection?</title>
      <link>https://emil.lerch.org/2011-06-13-eduspring-part-4-what-is-so-terribly-broken-with-dependency-injection/</link>
      <pubDate>Mon, 13 Jun 2011 22:23:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2011-06-13-eduspring-part-4-what-is-so-terribly-broken-with-dependency-injection/</guid>
      <description>&lt;p&gt;Move all the CS BS to the side.  This stuff doesn&amp;rsquo;t work in the real
world.  And it&amp;rsquo;s because of things that could have (and probably should
have) been fixed by uber-geeks 10+ years ago with fancy CompSci PhDs.
 And I&amp;rsquo;m talking about .Net specifically here, although I can throw the
same stones at Java.  Most (but not all) other languages have the same
problems I&amp;rsquo;ll get on my soapbox about here.&lt;/p&gt;
&lt;p&gt;To do DI correctly, you have one of two options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your class has dependencies on properties/internal fields that
implement Interfaces&lt;/li&gt;
&lt;li&gt;Your class has dependencies on properties/internal fields that
implement base classes&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In both cases, the contracts are relatively weak.  If you depend on a
method that requires an object and returns an object, there are lots of
things that can go wrong.  And you have no way of knowing whether it
will go wrong unless an until you run it - compile time checks don&amp;rsquo;t
help.  Here&amp;rsquo;s a short list of Murphy&amp;rsquo;s law for a single method that
takes an object parameter and returns an object:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Do you assume anything about the return value?  It might return what
you expect, or might return null.  Or, it could throw an exception.
 Generally, experience with DI will teach you good defensive coding,
but it does take work&amp;hellip;&lt;/li&gt;
&lt;li&gt;Can the method handle null input values?  If it errors, is it going
to return null or throw an exception?  If it throws, what kind of
exception will it generate?&lt;/li&gt;
&lt;li&gt;Is the method you&amp;rsquo;re calling going to muck with the object you pass
in?  If it does alter properties, is that a problem?  What if you&amp;rsquo;re
not in control of the object, and the property this dependency
decides it&amp;rsquo;s OK to muck with throws an exception during the set
operation?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Without some type of &lt;a href=&#34;http://en.wikipedia.org/wiki/Design_by_contract&#34;&gt;design by
contract&lt;/a&gt; construct
built into your language of choice, these questions become just the tip
of the iceberg.  Glancing through the Common Language Specification for
.NET, it appears that there is no built-in construct available in the
platform.  C# provides a MS Research Code Contract construct in .NET 4,
but they feel hacky without being part of the language, and even more
hacky when implemented with IoC.  &lt;/p&gt;
&lt;p&gt;Feel free to run through the Main method for FallaciesOfInterfaces for
concrete examples of how Murphy can strike.  This project will compile,
but will fail at almost every step.  Read through the comments and fix
the code one-by-one to get a sense of how, even with an IoC container
and good DI practices, everything must be tested.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>EduSpring Part 3: What do we need an IoC for?</title>
      <link>https://emil.lerch.org/2011-06-13-eduspring-part-3-what-do-we-need-an-ioc-for/</link>
      <pubDate>Mon, 13 Jun 2011 22:05:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2011-06-13-eduspring-part-3-what-do-we-need-an-ioc-for/</guid>
      <description>&lt;p&gt;This post is part of a
&lt;a href=&#34;http://emilsblog.lerch.org/search/label/EduSpring&#34;&gt;series&lt;/a&gt; on
Spring.NET. I recommend starting at the beginning if you haven&amp;rsquo;t
already.  Also, I am walking through code in the &lt;a href=&#34;https://github.com/elerch/eduSpring&#34;&gt;accompanying GitHub
project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Last time, I walked through why we might want to use dependency
integration.  Outside the authentication example, here are a few other
examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Authorization&lt;/li&gt;
&lt;li&gt;SMTP settings&lt;/li&gt;
&lt;li&gt;Payment gateways&lt;/li&gt;
&lt;li&gt;Business rules (rule engine style)&lt;/li&gt;
&lt;li&gt;Branding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This time, I&amp;rsquo;d like to explore how an IoC container actually does its
job.  By understanding how the IoC does its job, we can understand the
value that it adds to our solutions. In the project &amp;ldquo;&lt;a href=&#34;https://github.com/elerch/eduSpring/tree/master/2%20-%20IocWithoutSpring&#34;&gt;2 -
IocWithoutSpring&lt;/a&gt;&amp;rdquo;
on GitHub, you&amp;rsquo;ll see a quick, hand-coded IoC container.  It&amp;rsquo;s amazingly
brilliant (insert sarcasm here) and also relatively close to what a real
IoC container does (really).  Here is the IocContainer class in all it&amp;rsquo;s
glory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class IoCContainer
    {
        private readonly IDictionary _allObjects = new Dictionary();

        public T GetObject(string objectName)
        {
            return (T)_allObjects[objectName];
        }

        public void Initialize()
        {
            // Wouldn&#39;t it be nice if we could configure this through 
            // app.config or web.config?  Spring does that!
            _allObjects.Add(&amp;quot;myObject&amp;quot;, new ClassA());
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most IoC containers work in a similar manner. There&amp;rsquo;s some sort of
initialization function, and there&amp;rsquo;s some sort of GetObject method.
 Internally, all it does is &amp;ldquo;new up&amp;rdquo; all the classes you&amp;rsquo;ve defined and
shoves them in a global dictionary object.  The rest, as they say, is
window dressing.  As you see in the comment, once you&amp;rsquo;ve built an IoC
container, you can think of all kinds of cool things to do.  It would be
really nice if you could configure from web.config/app.config, or maybe
a separate XML file, or maybe the database, or maybe all of them?  What
if you added the ability to set properties on the objects, not just
create them?  How about the ability to create an object based on the
state of another object (Spring calls this a factory method).  How
about defining objects in one place, and values for properties in
another?  IoC containers have all this stuff, and it&amp;rsquo;s built for
you&amp;hellip;there&amp;rsquo;s no reason to reinvent the wheel.&lt;/p&gt;
&lt;p&gt;But at the end of the day, remember: An IoC container, at its heart, is
a Dictionary&amp;lt;string, object&amp;gt;.  And that&amp;rsquo;s all, folks.  Another key
thing to remember, especially in a stateless ASP.NET scenario, is that
those objects exist during the lifetime of the application.  This is a
performance gain (no GC stepping in, no creation of multiple objects),
and a bug factory if you assume your objects are going to be created,
used once, and destroyed.&lt;/p&gt;
&lt;p&gt;More goodies are in the &lt;a href=&#34;https://github.com/elerch/eduSpring/blob/master/2%20-%20IocWithoutSpring/IoCWithoutSpring.cs&#34;&gt;main
file&lt;/a&gt;
if you read the comments, but I&amp;rsquo;ll explore them more in depth in later
posts.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>EduSpring Part 2: Simplifying dependencies</title>
      <link>https://emil.lerch.org/2011-06-13-eduspring-part-2-simplifying-dependencies/</link>
      <pubDate>Mon, 13 Jun 2011 17:25:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2011-06-13-eduspring-part-2-simplifying-dependencies/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;http://emilsblog.lerch.org/2011/06/eduspring-part-1-introduction.html&#34;&gt;Last
time&lt;/a&gt;,
I introduced you to Spring, IoC, and Dependency Injection.  If you
haven&amp;rsquo;t read that post, please do. This time, I&amp;rsquo;ll start walking through
some of the code in the &lt;a href=&#34;https://github.com/elerch/eduSpring&#34;&gt;accompanying GitHub
project&lt;/a&gt;.  The code is organized by
project in order of these posts.  Some conventions (specifically 1
class/file) have been ignored specifically to let the reader go through
in a linear manner.  All projects are setup as console applications.&lt;/p&gt;
&lt;p&gt;This time, I&amp;rsquo;ll tackle the first project, &amp;ldquo;0 - Advantages of DI&amp;rdquo;.  This
is one of the few projects I that will have more than one file to
peruse.  The first file is a baseline&amp;hellip;how we would solve a particular
problem in a &amp;ldquo;traditional&amp;rdquo; procedural manner.  I&amp;rsquo;ve simulated a program
that calls a service (MyClass) that needs to authenticate the user prior
to performing some work on behalf of the caller.  If authentication
fails, it will throw an exception.  Here is the class:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// 
    /// Simulates a typical class
    /// 
    public class MyClass
    {
        /// 
        /// Authentication provider.  We can&#39;t change this out for the production version 
        /// without changing source code and recompiling
        /// 
        private readonly MyAuthenticationProvider authenticationProvider = new MyAuthenticationProvider();

        // Comment the line above, uncomment and recompile for production.  
        // 
        // By recompiling, you won&#39;t know if you&#39;re testing the same
        // code that exists in production (did you just change this line, or something else too?)  Versions might 
        // be different, etc.
        //private readonly MyProductionAuthenticationProvider authenticationProvider = new MyProductionAuthenticationProvider();

        public void DoWork(string user, SecureString password)
        {
            if(!authenticationProvider.Authenticate(user,password).Identity.IsAuthenticated)
                throw new SecurityException(&amp;quot;Not authorized to perform this action&amp;quot;);
            Thread.Sleep(3000);// Simulate some work
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&amp;rsquo;ll notice that when we move this class into production, we have to
remember to swap out our authentication provider. In reality, the new
authentication provider might now call a central authentication
single-sign-on service through a web/rest service, might redirect the
user in order to change password, or any number of crazy things. For
these purposes, I just check that the password is &amp;ldquo;bar&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The main point here is that we have to remember to swap, and we&amp;rsquo;ll have
to recompile the application when we do that.  If we forget, anyone can
log in with anything and we&amp;rsquo;ll happily give up the goods.  Since
everything has been &amp;ldquo;tested&amp;rdquo;, it&amp;rsquo;s likely we won&amp;rsquo;t thoroughly check the
service (indeed, if we&amp;rsquo;re on a production system, and this service&amp;rsquo;s
purpose was to delete a bunch of accounts, we probably wouldn&amp;rsquo;t test
it).  After deployment have two versions of the binary - one for
production, and one outside production.  Who is to say there aren&amp;rsquo;t any
other changes in this code.  Now we have a maintenance problem too - we
can&amp;rsquo;t assume the code is identical, and every time we deploy we run the
risk of missing this step.  But, it&amp;rsquo;s simpler when we&amp;rsquo;re coding it,
isn&amp;rsquo;t it?&lt;/p&gt;
&lt;p&gt;If we could just pass this dependency into MyClass, we&amp;rsquo;ll be in much
better shape.  At the minimum we can centralize the changes, and in the
best case we can move these changes out into a configuration file or
some other mechanism that doesn&amp;rsquo;t require code changes for this simple
switch.  Enter the next file in this project - &amp;ldquo;1 -
WithDependencyInjection.cs&amp;rdquo;.  Here are the contents:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/// 
    /// Simulates a typical class
    /// 
    public class MyClassWithDependencyInjection
    {
        /// 
        /// Authentication provider.  Because we use an interface, we no longer 
        /// need to change this code after it&#39;s been tested
        /// 
        private readonly IAuthenticate _authenticationProvider;

        /// 
        /// Constructor to establish this class&#39; dependencies.  Since the 
        /// class is not &amp;quot;complete&amp;quot; (can&#39;t operate) without an authentication
        /// provider, we require an object up front.
        /// 
        /// public MyClassWithDependencyInjection(IAuthenticate authProvider)
        {
            if (authProvider == null)
                throw new ArgumentNullException(&amp;quot;authProvider&amp;quot;);
            _authenticationProvider = authProvider;
        }

        public void DoWork(string user, SecureString password)
        {
            if (!_authenticationProvider.Authenticate(user, password).Identity.IsAuthenticated)
                throw new SecurityException(&amp;quot;Not authorized to perform this action&amp;quot;);
            Thread.Sleep(3000);// Simulate some work
        }
    }

    /// 
    /// New Interface introduced.  Our two classes don&#39;t have a real-world inheritance relationship,
    /// but the authentication process is identical, so we build a contract
    /// 
    public interface IAuthenticate
    {
        IPrincipal Authenticate(string user, SecureString password);
    }

    class TestingAuthenticationProvider : IAuthenticate
    {
        public IPrincipal Authenticate(string user, SecureString password)
        {
            // Implementation for development
            return new GenericPrincipal(new GenericIdentity(user), new string[] { });
        }
    }

    class ProductionAuthenticationProvider : IAuthenticate
    {
        public IPrincipal Authenticate(string user, SecureString password)
        {
            if (user == &amp;quot;foo&amp;quot; &amp;amp;&amp;amp; password.StringEquals(&amp;quot;bar&amp;quot;))
                return new GenericPrincipal(new GenericIdentity(user), new string[] { });
            return new GenericPrincipal(new GenericIdentity(&amp;quot;&amp;quot;), new string[] { }); ;
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we&amp;rsquo;ve done the following things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Introduced an interface for authentication and marked both providers
as implementing the interface.  We could just as well have
introduced a base class (concrete or abstract), but we have no
default implementation, so an interface is probably best.&lt;/li&gt;
&lt;li&gt;Introduced a parameterized constructor for the main class.  Now this
class lets the world know that it can&amp;rsquo;t do it&amp;rsquo;s job without a way to
authenticate.  And it doesn&amp;rsquo;t care how the authentication gets done,
just that it &lt;strong&gt;can&lt;/strong&gt; authenticate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Back in the main program, we can now (sort of) demonstrate what we&amp;rsquo;ve
done.  Obviously without changing the code we can&amp;rsquo;t demonstrate changing
the provider for MyClass, but we can demonstrate the DI approach to the
problem:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static void Main(string[] args)
        {
            // Without DI
            new MyClass().DoWork(&amp;quot;foo&amp;quot;, new SecureString().Append(&amp;quot;bar&amp;quot;));
            
            // With DI
            var testSystemProvider = new TestingAuthenticationProvider();
            var productionSystemProvider = new ProductionAuthenticationProvider();
            // The choice of test/production is now made here, outside of the class.  
            // The class shouldn&#39;t &amp;quot;care&amp;quot; who does the authentication work, it&#39;s job is to do other work
            // The new statement in the next line would be done through an IoC container (e.g. Spring) in a 
            // real environment.
            new MyClassWithDependencyInjection(testSystemProvider).DoWork(&amp;quot;foo&amp;quot;, new SecureString().Append(&amp;quot;bar&amp;quot;));

            // Switch the constructor (in Spring.NET, done through a config file change) and now we&#39;re running against 
            // &amp;quot;production&amp;quot;.  No recompile necessary
            new MyClassWithDependencyInjection(productionSystemProvider).DoWork(&amp;quot;foo&amp;quot;, new SecureString().Append(&amp;quot;bar&amp;quot;));

            // We can also do this without a container.  Here, we&#39;ll get type information from the command line.
            // Change the provider type via project properties to see how this works.
            // The next line is basically what an IoC container would do
            var myAuthProvider = (IAuthenticate)Activator.CreateInstance(Type.GetType(args[0]));
            new MyClassWithDependencyInjection(myAuthProvider).DoWork(&amp;quot;foo&amp;quot;, new SecureString().Append(&amp;quot;bar&amp;quot;));
        }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With dependency injection techniques, the caller can decide the
authentication mechanism.  Given a proper IoC container, this
configuration will be centralized, but for now, we can do it manually by
creating two objects, one of type TestingAuthenticationProvider and one
of type ProductionAuthenticationProvider.  These are passed into the
constructor for the DI class, and voilà, we can change the way
authentication works.  In the last two lines, I show how we can even
pass in the type name via the command line.  Now, for the first time, we
have the ability to change the way we authenticate without changing
code.&lt;/p&gt;
&lt;p&gt;&amp;hellip;and that, folks, is all an IoC container is about.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>EduSpring Part 1: Introduction</title>
      <link>https://emil.lerch.org/2011-06-13-eduspring-part-1-introduction/</link>
      <pubDate>Mon, 13 Jun 2011 16:46:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2011-06-13-eduspring-part-1-introduction/</guid>
      <description>&lt;p&gt;I get quite a few questions about
&lt;a href=&#34;http://springframework.net/&#34;&gt;Spring.Net&lt;/a&gt;, so I thought I&amp;rsquo;d put together
a VS Solution, presentation, and set of blog posts to provide some
background and details about what it is, why/when to consider using it,
and how to configure and debug the framework.&lt;/p&gt;
&lt;p&gt;A lot of this material will &lt;strong&gt;not&lt;/strong&gt; be specific to Spring.Net, but
rather &lt;a href=&#34;http://en.wikipedia.org/wiki/Dependency_injection&#34;&gt;Dependency
Injection&lt;/a&gt; generally.
 To understand Spring.Net, you must be familiar with &lt;a href=&#34;http://en.wikipedia.org/wiki/Inversion_of_control&#34;&gt;Inversion of
Control&lt;/a&gt; and
Dependency Injection.  You can read about them on Wikipedia, but here&amp;rsquo;s
a short summary:&lt;/p&gt;
&lt;p&gt;Inversion of Control: Flow of control of a system is inverted in
comparison to procedural programming&lt;br&gt;
Dependency Injection: A specific technique to achieve inversion of
control with respect to dependencies&lt;/p&gt;
&lt;p&gt;What this means in layman&amp;rsquo;s terms is that instead of a program:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Starts up&lt;/li&gt;
&lt;li&gt;Creating new services for whatever needs to be done&lt;/li&gt;
&lt;li&gt;Using the services/doing the work&lt;/li&gt;
&lt;li&gt;Disposing of the services and returning the results&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The program now does only what it&amp;rsquo;s responsible.  Namely:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Starts up&lt;/li&gt;
&lt;li&gt;Uses services that have been configured for its use/does the work&lt;/li&gt;
&lt;li&gt;Returns the results&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This makes the program itself easier to test, simpler to code/maintain,
and separates infrastructural responsibilities (determine location of
services and creating them) from the business logic the program was
created to deliver.&lt;/p&gt;
&lt;p&gt;The devil in these particular details are in #2.  How does one create
and configure services for a program?  We can code this by hand, but
it&amp;rsquo;s a lot of boring glue code, and doing it right is incredibly
difficult.  Enter the &lt;a href=&#34;http://www.martinfowler.com/articles/injection.html&#34;&gt;IoC
container&lt;/a&gt; - a
piece of pre-built code that will do this work for us.  Depending on the
application, we don&amp;rsquo;t even need to use the container explicitly.  In a
system like &lt;a href=&#34;http://www.asp.net/&#34;&gt;ASP.NET&lt;/a&gt;, we can use
&lt;a href=&#34;http://msdn.microsoft.com/en-us/library/system.web.ihttpmodule.aspx&#34;&gt;HttpModules&lt;/a&gt;
to intercept requests and wire dependencies automatically.&lt;/p&gt;
&lt;p&gt;One significant problem with this approach are the complexity of
configuration.  Using Spring.net, I can have a dependency-injected
factory method to deliver an instance of an object used to populate a
property that will be injected into another object that will have
additional advice applied through Spring.Net&amp;rsquo;s AOP module.  When that
fails, I might get a &amp;ldquo;node cannot be resolved for the specified context&amp;rdquo;
without any additional information pointing me to the cause of the
problem.  With flexibility comes complexity.&lt;/p&gt;
&lt;p&gt;Another issue is a fundamental problem with .NET (C# and VB and I
believe all .NET classes adhering to the
&lt;a href=&#34;http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-335.pdf&#34;&gt;CLI&lt;/a&gt;).
 Interfaces are incomplete contracts.  &lt;/p&gt;
&lt;p&gt;Finally, with regards to Spring.Net in particular, the framework can be
slow to move (IIS7 Integrated mode, introduced with Windows Server 2008
on Feb 27, 2008, gained support in Spring.Net 1.3.1 released December
10, 2010).  Also, no &lt;a href=&#34;http://forum.springframework.net/showthread.php?t=6875&#34;&gt;Mono
support&lt;/a&gt; exists
as of this writing.&lt;/p&gt;
&lt;p&gt;Next up - some walkthrough code.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Total application design: circa 2011</title>
      <link>https://emil.lerch.org/2011-06-05-total-application-design-circa-2011/</link>
      <pubDate>Sun, 05 Jun 2011 23:01:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2011-06-05-total-application-design-circa-2011/</guid>
      <description>&lt;p&gt;Working with a few recent projects I&amp;rsquo;m getting pretty close to a nice
boilerplate for a &amp;ldquo;standard&amp;rdquo; web application built with the latest
technologies.  Subject to client restraints, here&amp;rsquo;s the stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UI and middle tier: &lt;a href=&#34;http://www.asp.net/mvc&#34;&gt;ASP.NET MVC3&lt;/a&gt; (using
VS2010 SP1).  Note that this requires .NET 4, which has some nice
features (I love Tuples) but I don&amp;rsquo;t think is completely necessary
otherwise.  SP1 is important, which I&amp;rsquo;ll mention later.  For
smaller-ish applications I&amp;rsquo;ve been moving away from multiple
projects in a solution, mainly because I haven&amp;rsquo;t been seeing a ton
of real business logic besides authorization, and I&amp;rsquo;ve been able to
productize many ancillary functions into separate DLLs.  Razor view
engine is used, and templates are updated to mirror
&lt;a href=&#34;http://html5boilerplate.com/&#34;&gt;Html5Boilerplate.com&lt;/a&gt;&amp;rsquo;s templates.
jQuery is applied unobtrusively.&lt;/li&gt;
&lt;li&gt;Data access: &lt;a href=&#34;http://fluentnhibernate.org/&#34;&gt;Fluent NHibernate&lt;/a&gt;.
 This is integrated in such a way that depending on your needs, you
don&amp;rsquo;t need to add a direct reference to it, though.  References are
needed for access to NHibernate advanced queries and the Linq
provider.&lt;/li&gt;
&lt;li&gt;URL Rewriting: &lt;a href=&#34;http://urlrewriter.net/&#34;&gt;UrlRewriter.Net&lt;/a&gt;. This is
used for css/js versioning.  A
&lt;a href=&#34;http://msdn.microsoft.com/en-us/library/system.web.mvc.urlhelper.aspx&#34;&gt;URLHelper&lt;/a&gt;
extension method appends the current version number (derived at
build time from source control) for the application to the script or
CSS filename, and the rewriter strips it back off.  We probably
could use MVC3 routes here, but it seems overkill for something that
could be handled by simple Url rewriting.&lt;/li&gt;
&lt;li&gt;Dependency Injection: &lt;a href=&#34;http://www.springframework.net/&#34;&gt;Spring.net&lt;/a&gt;.
 Any DI/IOC container would work here.  Again, the integration with
spring is very lightweight and easily replaceable.  One strike
against Spring.Net is the lack of support for Mono, but this hasn&amp;rsquo;t
been an issue in the environments with which I&amp;rsquo;ve been working.&lt;/li&gt;
&lt;li&gt;Other tools: &lt;a href=&#34;http://www.dotlesscss.org/&#34;&gt;Dotless&lt;/a&gt; is used in a
development-time capacity to generate CSS, with
&lt;a href=&#34;http://chirpy.codeplex.com/&#34;&gt;Chirpy&lt;/a&gt; (&lt;a href=&#34;https://hg01.codeplex.com/forks/etlerch/directorybasedsettingswithinchirp&#34;&gt;customized&lt;/a&gt;)
to keep the development experience smooth.  A handful of other
custom DLLs and build tools keep the project structure relatively
vanilla.  &lt;a href=&#34;http://hudson-ci.org/&#34;&gt;Hudson&lt;/a&gt; is used for continuous
integration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m putting together a NuGet package with this build.  When this is
complete, I&amp;rsquo;ll post an update.&lt;/p&gt;
&lt;p&gt;Other technologies/tools on the radar are &lt;a href=&#34;http://msdn.microsoft.com/en-us/library/aa697427(v=vs.80).aspx&#34;&gt;Entity
Framework&lt;/a&gt;
(NHibernate replacement) and &lt;a href=&#34;http://www.castleproject.org/&#34;&gt;Castle
Windsor&lt;/a&gt; (IoC container that supports
Mono).  &lt;a href=&#34;http://www.dotnetopenauth.net/&#34;&gt;DotNetOpenAuth&lt;/a&gt; is under
consideration as a standard way to pull in authentication and
authorization functions.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Supporting Multiple Databases in Applications</title>
      <link>https://emil.lerch.org/2011-03-29-supporting-multiple-databases-in-applications/</link>
      <pubDate>Tue, 29 Mar 2011 08:48:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2011-03-29-supporting-multiple-databases-in-applications/</guid>
      <description>&lt;p&gt;In general, having a persistence-ignorant application provides a lot of
flexibility.  Allowing us to easily port between various RDBMS’s, NoSQL
data stores, text files, the cloud, or simply storing our data on
papyrus managed and maintain by a group of beer-making monks provides us
with tangible opportunities to cross-sell our (non-hosted) application
to multiple clients.&lt;/p&gt;
&lt;p&gt;As any write once, run anywhere scheme, support for multiple databases
can be notoriously difficult.  Consider the following SQL Server T-SQL
code:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./posts/2011-03-29/2011-03-29-supporting-multiple-databases-in-applications-Table%20name%20sensitivity.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Wait…what?  I just created FOO, yet SQL Server is telling me that the
table foo doesn’t exist.  What’s going on?  In this case, the database
&lt;a href=&#34;http://msdn.microsoft.com/en-us/library/ms175835.aspx&#34;&gt;default
collation&lt;/a&gt; has
been changed to Latin1_General_Bin, a binary collation, so FOO !=
‘foo’.  As a result, the following query also provides no results:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./posts/2011-03-29/2011-03-29-supporting-multiple-databases-in-applications-Case%20sensitivity%20at%20data%20level.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;To get any data back from this table, we need to match exactly:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;./posts/2011-03-29/2011-03-29-supporting-multiple-databases-in-applications-Sucess.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Great, so I’ve jacked the default installation collation in SQL Server. 
Not a lot of people or organizations do this?  But…is this an academic
exercise?  If you want to support multiple databases and clients, the
answer is a resounding NO.  Consider:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Some organizations may want the application to work on a database
with binary collation.  Rare, but it could happen.&lt;/li&gt;
&lt;li&gt;Organizations might be using another database, and not all databases
are case insensitive, even by default.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you want a portable application, #2 is your much more concerning
issue.  Recently I verified default installations on a number of popular
database systems to determine what behavior they produced.  Here are my
findings (in alphabetical order):&lt;/p&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;p&gt;The most curious finding is MySQL.  I’d love to know what reasoning was
used to come to the conclusion that Table and Column names should be
case sensitive, but data should be considered insensitive.  In any case,
programming for the least common denominator requires handling these
situations. &lt;/p&gt;
&lt;p&gt;Ideally you’d leave the SQL to an ORM.  If your application is unlikely
to switch database platforms and an ORM is out of the question, I’d
recommend a RDBMS-specific data access layer.  If you’re application
absolutely must have a generic data layer, though, you must keep this in
mind as part of design.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Batch uploading RDL files to SQL Server Reporting Services 2005</title>
      <link>https://emil.lerch.org/2010-03-22-batch-uploading-rdl-files-to-sql-server-reporting-services-2005/</link>
      <pubDate>Mon, 22 Mar 2010 07:10:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2010-03-22-batch-uploading-rdl-files-to-sql-server-reporting-services-2005/</guid>
      <description>&lt;p&gt;There doesn&amp;rsquo;t seem to be a tool to mass-upload all RDL files in a
directory, so I created one. It&amp;rsquo;s available on BitBucket here:
&lt;a href=&#34;http://bitbucket.org/emil/bulk-rdl-uploader-for-ssrs-reports/overview/&#34;&gt;http://bitbucket.org/emil/bulk-rdl-uploader-for-ssrs-reports/overview/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The executable is available here:
&lt;a href=&#34;http://bitbucket.org/emil/bulk-rdl-uploader-for-ssrs-reports/downloads/ReportingServicesBatchUpload.exe&#34;&gt;http://bitbucket.org/emil/bulk-rdl-uploader-for-ssrs-reports/downloads/ReportingServicesBatchUpload.exe&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is a console application with no dependencies (it calls a web
service). Call without parameters for usage information. One known bug
is that the &amp;ldquo;create a folder if it doesn&amp;rsquo;t already exist&amp;rdquo; logic seems to
be broken for some servers, so for best results create the reporting
services folder prior to running the program.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Working with a feature branch in Subversion (TortoiseSVN on Windows)</title>
      <link>https://emil.lerch.org/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows/</link>
      <pubDate>Tue, 09 Mar 2010 08:51:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows/</guid>
      <description>&lt;p&gt;Branching and merging is one of the most nerve-racking activities for
people working with Subversion.  Unlike Mercurial and other DVCS where
branching and merging is commonplace, typical workflows in Subversion do
not include this activity.  As a result, the terminology is typically
confusing, the massive number of changes can be scary, and a lot of
people are so concerned about &amp;ldquo;getting it right&amp;rdquo; that they typically
just avoid the practice altogether and work completely outside of source
control.  That, of course, is the exact opposite of what we need when
working on an important design spike or critical new feature.&lt;/p&gt;
&lt;p&gt; Conceptually, what we want with a design spike or large new feature is
to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new branch for the change&lt;/li&gt;
&lt;li&gt;Work in the new branch, committing early and often&lt;/li&gt;
&lt;li&gt;Periodically pull in (merge) the changes from the project trunk&lt;/li&gt;
&lt;li&gt;When complete, reintegrate the branch into the trunk&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For these instructions, I assume that you have a subversion repository
checked out and you&amp;rsquo;re actively working on the trunk.&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;&lt;strong&gt;Creating a new branch:&lt;/strong&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-03-09/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.17+-+003.png&#34;&gt;&lt;img src=&#34;./posts/2010-03-09/thumbnails/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.17+-+003.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt; Right-click on the directory you want to branch.  Pick the
Branch/Tag operation.&lt;/li&gt;
&lt;li&gt;On the branch/tag screen, enter a new branch (typically the
repository has a /branches folder as well as a /trunk folder).  The
new branch is typically /branches/&lt;em&gt;mynewbranch&lt;/em&gt;, where the branch
name itself is a folder that does not exist in the repository.&lt;/li&gt;
&lt;li&gt;In this scenario, you will also want to choose &amp;ldquo;switch working copy
to new branch/tag&amp;rdquo;.  We&amp;rsquo;ll be doing work on the new feature or
design change immediately, so this tells subversion that any new
commits will be on that other branch.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-03-09/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.22+-+004.png&#34;&gt;&lt;img src=&#34;./posts/2010-03-09/thumbnails/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.22+-+004.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; At this point you&amp;rsquo;ll want to take note of the revision number that had
the last changes.&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;&lt;strong&gt;Work in the new branch, committing
early and often&lt;/strong&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;This is standard subversion behavior.&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;&lt;strong&gt;Periodically pull in (merge) the
changes from the project trunk&lt;/strong&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll want to keep track of the last revision you&amp;rsquo;ve pulled changes
from.  In the beginning, this revision number is the revision that
represented the branch itself.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-03-09/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.32+-+005.png&#34;&gt;&lt;img src=&#34;./posts/2010-03-09/thumbnails/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.32+-+005.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Pick the Merge tool off the TortoiseSVN menu.&lt;/li&gt;
&lt;li&gt;Choose the &amp;ldquo;Merge a range of revisions&amp;rdquo; from the first screen of the
wizard.&lt;/li&gt;
&lt;li&gt;In the &amp;ldquo;URL to merge from&amp;rdquo;, choose the trunk.&lt;/li&gt;
&lt;li&gt;In the Revision range to merge, the best approach is to be precise. 
Put in the last revision number that was merged (e.g. if you
branched when the repository was at revision 10, put in 11-HEAD). 
You can leave this blank, but we want to avoid merging a change
twice.  You may need to look at the log to help find the right
revisions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-03-09/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.37+-+006.png&#34;&gt;&lt;img src=&#34;./posts/2010-03-09/thumbnails/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.37+-+006.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Hit next and do a test merge just to be sure before performing the
actual merge.  All other settings can be left at their defaults.  Note
that the merge is done in your working directory, so you&amp;rsquo;ll have a bunch
of local changes to test and commit on your branch.&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;&lt;strong&gt;Reintegrate the branch into the
trunk&lt;/strong&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-03-09/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.47+-+007.png&#34;&gt;&lt;img src=&#34;./posts/2010-03-09/thumbnails/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.47+-+007.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Commit all branch changes&lt;/li&gt;
&lt;li&gt;Switch the working directory back to the trunk.  This is done via
the TortoiseSVN switch command.&lt;/li&gt;
&lt;li&gt;Pick the Merge command off the TortoiseSVN menu as before.  This
time, however, we&amp;rsquo;ll pick the &amp;ldquo;Reintegrate a branch&amp;rdquo; option.&lt;/li&gt;
&lt;li&gt;In the &amp;ldquo;From URL&amp;rdquo; field, put the URL to the branch you&amp;rsquo;ve finished
working on.&lt;/li&gt;
&lt;li&gt;Test the merge and if happy, perform the merge, test the changes,
and commit to the trunk.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-03-09/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.48+-+008.png&#34;&gt;&lt;img src=&#34;./posts/2010-03-09/thumbnails/2010-03-09-working-with-a-feature-branch-in-subversion-tortoisesvn-on-windows-Magical+Snap+-+2010.03.09+08.48+-+008.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Spring.Net-enabled WCF Services available from Microsoft Ajax</title>
      <link>https://emil.lerch.org/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax/</link>
      <pubDate>Thu, 25 Feb 2010 17:39:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;./posts/2010-02-25/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+16.58+-+005.png&#34;&gt;&lt;img src=&#34;./posts/2010-02-25/thumbnails/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+16.58+-+005.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Implementing Spring.NET WCF services is fairly straightforward.&lt;/p&gt;
&lt;p&gt;Implementing MS Ajax WCF services is also straightforward, if you pick
the right New Item to add from Visual Studio.&lt;/p&gt;
&lt;p&gt;The complication comes in when you want a Spring.NET WCF service that
handles calls from Microsoft Ajax controls.  This method will let you
add them. &lt;/p&gt;
&lt;p&gt;Step 1. Add new &amp;ldquo;Ajax-Enabled WCF Service&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-02-25/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+16.59+-+006.png&#34;&gt;&lt;img src=&#34;./posts/2010-02-25/thumbnails/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+16.59+-+006.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Step 2. Create your methods, test and make sure all base functionality
is working.  This sample is for a CascadingDropDown control from the
Ajax Control Toolkit, but any control will work.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-02-25/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.03+-+007.png&#34;&gt;&lt;img src=&#34;./posts/2010-02-25/thumbnails/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.03+-+007.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-02-25/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.06+-+011.png&#34;&gt;&lt;img src=&#34;./posts/2010-02-25/thumbnails/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.06+-+011.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Step 3. Introduce an interface to use for the methods.  Spring.Net
requires an interface, and this is the crux of the problem.  Here I&amp;rsquo;ve
created IMyAjaxService.&lt;/p&gt;
&lt;p&gt;Step 4. Move the [ServiceContract(Namespace = &amp;ldquo;&amp;rdquo;)] from the class to
the interface&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-02-25/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.09+-+012.png&#34;&gt;&lt;img src=&#34;./posts/2010-02-25/thumbnails/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.09+-+012.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Step 5. Move the [OperationContract] tags from the methods on the
class to the interface method definitions&lt;/p&gt;
&lt;p&gt; &lt;a href=&#34;./posts/2010-02-25/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.10+-+013.png&#34;&gt;&lt;img src=&#34;./posts/2010-02-25/thumbnails/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.10+-+013.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Step 6. Change web.config endpoint contract (xpath =
/configuration/system.serviceModel/services/service/endpoint) to
reference the interface&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-02-25/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.13+-+015.png&#34;&gt;&lt;img src=&#34;./posts/2010-02-25/thumbnails/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.13+-+015.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Step 7: Test. The test should work, without dependency injection from
Spring.Net.  Now we have a WCF service that responds to Ajax, but has
the interface definitions just the way Spring likes them.&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;Wiring in
Spring&lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;For Spring.NET to handle WCF, you need .NET 3.0 or higher, and you need
Spring.Net 1.3.0 or higher.  You&amp;rsquo;ll need the following DLLs available
for binding (either your bin directory or the GAC):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Spring.Core.dll from the 2.0 folder&lt;/li&gt;
&lt;li&gt;Spring.Web.dll from the 2.0 folder&lt;/li&gt;
&lt;li&gt;Spring.Services.dll from the 3.0 folder&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the Service you&amp;rsquo;ve created, edit the markup and add
Factory=&amp;ldquo;Spring.ServiceModel.Activation.ServiceHostFactory&amp;rdquo; onto the
end, like so:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2010-02-25/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.14+-+016.png&#34;&gt;&lt;img src=&#34;./posts/2010-02-25/thumbnails/2010-02-25-spring-net-enabled-wcf-services-available-from-microsoft-ajax-Magical+Snap+-+2010.02.25+17.14+-+016.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This will get Spring.Net into the activation pipeline for the service.&lt;/p&gt;
&lt;p&gt;Lastly, you&amp;rsquo;ll add the new object to spring configuration.  It is &lt;!-- raw HTML omitted --&gt;CRITICAL that the OBJECT ID MATCH THE
SERVICE NAME FROM WEB.CONFIG.  The Spring.Net documentation mentions
this, but I don&amp;rsquo;t think they mention it very loudly.  The type
information is exactly as you&amp;rsquo;d expect: the type for the WCF service
class.&lt;!-- raw HTML omitted --&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;&lt;br&gt;
Also CRITICAL:  Add singleton=&amp;ldquo;false&amp;rdquo; to the object definition for
Spring for your new object&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Intermittent Operation Aborted Errors in IE when using MS Ajax</title>
      <link>https://emil.lerch.org/2009-07-08-intermittent-operation-aborted-errors-in-ie-when-using-ms-ajax/</link>
      <pubDate>Wed, 08 Jul 2009 22:50:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2009-07-08-intermittent-operation-aborted-errors-in-ie-when-using-ms-ajax/</guid>
      <description>&lt;p&gt;This issue has been plaguing our project: operation aborted errors,
intermittent in nature, occurring sometimes as little as once/week. Even
in IE 8 there were issues, although thankfully not the crazy dialog box
we see in IE 6 and 7.&lt;/p&gt;
&lt;p&gt;It turns out that the problem is due to a bug in Ajax itself. I won&amp;rsquo;t go
into all the details, since they&amp;rsquo;re covered very well in these two blog
posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First post with background:
&lt;a href=&#34;http://seejoelprogram.wordpress.com/2008/06/08/when-sysapplicationinitialize-causes-operation-aborted-in-ie/&#34;&gt;http://seejoelprogram.wordpress.com/2008/06/08/when-sysapplicationinitialize-causes-operation-aborted-in-ie/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Second post with bug fixes to the original bug fix:
&lt;a href=&#34;http://www.blogger.com/post-create.g?blogID=6692354#%20http://seejoelprogram.wordpress.com/2008/10/03/fixing-sysapplicationinitialize-again/&#34;&gt;http://seejoelprogram.wordpress.com/2008/10/03/fixing-sysapplicationinitialize-again/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I didn&amp;rsquo;t particularly care for the recommended way of packaging the fix,
so instead I&amp;rsquo;ve used built-in ASP.NET Ajax functionality to override the
default script delivery. I also updated the fixed functions to work for
all versions of Ajax (at the time of this writing - currently Ajax
bundled with .NET 3.5 SP1 and below). First, here are the updated
functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Sys$_Application$initialize:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; function Sys$_Application$initialize() {
     if(!this._initialized &amp;amp;&amp;amp; !this._initializing) {
         this._initializing = true;
         var u = window.navigator.userAgent.toLowerCase(),
             v = parseFloat(u.match(/.+(?:rv|it|ml|ra|ie)[\/: ]([\d.]+)/)[1]);

         var initializeDelegate = Function.createDelegate(this, this._doInitialize);

         if (/WebKit/i.test(u) &amp;amp;&amp;amp; v &amp;lt; 525.13)
         {
             this._load_timer = window.setInterval(function()
             {
                 if (/loaded|complete/.test(document.readyState))
                 {
                     initializeDelegate();
                 }
             }, 10);
         }
         else if (/msie/.test(u) &amp;amp;&amp;amp; !window.opera)
         {
             document.attachEvent(&#39;onreadystatechange&#39;,
                 function (e) {
                     if (e &amp;amp;&amp;amp; arguments.callee &amp;amp;&amp;amp; document.readyState == &#39;complete&#39;) {
                         document.detachEvent(&#39;on&#39;+e.type, arguments.callee);
                         initializeDelegate();
                     }
                 }
             );
             if (window == top) {
                 (function () {
                     try {
                         document.documentElement.doScroll(&#39;left&#39;);
                     } catch (e) {
                         setTimeout(arguments.callee, 10);
                         return;
                     }
                     initializeDelegate();
                 })();
             }

         }
         else if (document.addEventListener
             &amp;amp;&amp;amp;  ((/opera\//.test(u) &amp;amp;&amp;amp; v &amp;gt; 9) ||
                 (/gecko\//.test(u) &amp;amp;&amp;amp; v &amp;gt;= 1.8) ||
                 (/khtml\//.test(u) &amp;amp;&amp;amp; v &amp;gt;= 4.0) ||
                 (/webkit\//.test(u) &amp;amp;&amp;amp; v &amp;gt;= 525.13))) {
             document.addEventListener(&amp;quot;DOMContentLoaded&amp;quot;, initializeDelegate, false);
         }
         else
         {
             $addHandler(window, &amp;quot;load&amp;quot;, initializeDelegate);
         }
     }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sys$_Application$_doInitialize():&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; function Sys$_Application$_doInitialize() {
   if (this._initialized) {
    return;
   }
   Sys._Application.callBaseMethod(this, &#39;initialize&#39;);
   if (this._load_timer !== null)
   {
    clearInterval(this._load_timer);
    this._load_timer = null;
   }
   var handler = this.get_events().getHandler(&amp;quot;init&amp;quot;);
   if (handler) {
    this.beginCreateComponents();
    handler(this, Sys.EventArgs.Empty);
    this.endCreateComponents();
   }
   if (Sys.WebForms) {
    if (this._onPageRequestManagerBeginRequest) this._beginRequestHandler = Function.createDelegate(this, this._onPageRequestManagerBeginRequest);
    if (this._beginRequestHandler) Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(this._beginRequestHandler);
    if (this._onPageRequestManagerEndRequest) this._endRequestHandler = Function.createDelegate(this, this._onPageRequestManagerEndRequest);
    if (this._endRequestHandler) Sys.WebForms.PageRequestManager.getInstance().add_endRequest(this._endRequestHandler);
   }

   if (this.get_stateString){
    var loadedEntry = this.get_stateString();
    if (loadedEntry !== this._currentEntry) {
     this._navigate(loadedEntry);
    }
   }
   this.raiseLoad();
   this._initializing = false;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sys$_Application$_loadHandler():&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; function Sys$_Application$_loadHandler() {
  if(this._loadHandlerDelegate) {
   Sys.UI.DomEvent.removeHandler(window, &amp;quot;load&amp;quot;, this._loadHandlerDelegate);
   this._loadHandlerDelegate = null;
  }
  this._initializing = true;
  this._doInitialize();
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next, here is how I overrode how the framework delivers the script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;ajaxtoolkit:toolkitscriptmanager runat=&amp;quot;server&amp;quot; enablepartialrendering=&amp;quot;true&amp;quot; id=&amp;quot;ScriptManager&amp;quot;&amp;gt;
       &amp;lt;scripts&amp;gt;
           &amp;lt;asp:scriptreference name=&amp;quot;MicrosoftAjax.js&amp;quot; assembly=&amp;quot;System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35&amp;quot; path=&amp;quot;~/js/MicrosoftAjax-withFix.js&amp;quot;&amp;gt;
       &amp;lt;/scripts&amp;gt;
  &amp;lt;/ajaxToolkit:ToolkitScriptManager&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You do not need to use the ToolKitScriptManager - you can also do it
this way:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   &amp;lt;asp:scriptmanager runat=&amp;quot;server&amp;quot; enablepartialrendering=&amp;quot;true&amp;quot; id=&amp;quot;ScriptManager&amp;quot;&amp;gt;
       &amp;lt;scripts&amp;gt;
&amp;lt;asp:scriptreference name=&amp;quot;MicrosoftAjax.js&amp;quot; assembly=&amp;quot;System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35&amp;quot; path=&amp;quot;~/js/MicrosoftAjax-withFix.js&amp;quot;&amp;gt;
       &amp;lt;/scripts&amp;gt;
   &amp;lt;/asp:ScriptManager&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that you can replace ~/js with the directory of your choosing.&lt;/p&gt;
&lt;p&gt;Be aware that when the framework loads the file, it automatically adds
.debug or .release onto the end of the file name, so the final file
reference provided will be either MicrosoftAjax-withFix.debug.js or
MicrosoftAjax-withFix.release.js.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t want to bother incorporating these fixes into .NET 3.5 SP1
Ajax files, feel free to download them from here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Debug version:
&lt;a href=&#34;http://lerch.org/js/MicrosoftAjax-WithFix.debug.js&#34;&gt;http://lerch.org/js/MicrosoftAjax-WithFix.debug.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Release version (simply a minified version of the debug js - run
through &lt;a href=&#34;http://developer.yahoo.com/yui/compressor/&#34;&gt;YUI
Compresssor&lt;/a&gt;):
&lt;a href=&#34;http://lerch.org/js/MicrosoftAjax-WithFix.release.js&#34;&gt;http://lerch.org/js/MicrosoftAjax-WithFix.release.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Javascript Hacks: Using XHR to load binary data</title>
      <link>https://emil.lerch.org/2009-07-07-javascript-hacks-using-xhr-to-load-binary-data/</link>
      <pubDate>Tue, 07 Jul 2009 10:56:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2009-07-07-javascript-hacks-using-xhr-to-load-binary-data/</guid>
      <description>&lt;p&gt;I recently needed to get image data from a server using Javascript,
base64 encode it, and post that data back to an application. While the
details of &lt;!-- raw HTML omitted --&gt;why&lt;!-- raw HTML omitted --&gt; I needed to do
this are a bit complex, I believe that getting image data through an
XMLHttpRequest object and base 64 enconding it will become more valuable
in terms of client-side image manipulation using the &lt;a href=&#34;http://en.wikipedia.org/wiki/Data_URI_scheme&#34;&gt;data URI
scheme&lt;/a&gt; for image tags.&lt;/p&gt;
&lt;p&gt;This would allow a Javascript developer, for instance, to load an
existing image (say, a photo), without base64 encoding it on the server,
load it into an image tag with a data URI, and make direct manipulations
on that image.&lt;/p&gt;
&lt;p&gt;Unfortunately, this area is relatively new and browsers have a lot of
differences. Data URI support is still very new, inconsistent, and
limited. In the meantime, here is how you get that base64 encoded image
in the first place:&lt;/p&gt;
&lt;p&gt;Internet Explorer:&lt;/p&gt;
&lt;p&gt;IE has a property of XMLHttpRequest object for binary data
&lt;a href=&#34;http://msdn.microsoft.com/en-us/library/ms534368%28VS.85%29.aspx&#34;&gt;ResponseBody&lt;/a&gt;.
This contains exactly what we need, but unfortunately the property is
not visible to Javascript. Since the string returned to Javascript by
&lt;a href=&#34;http://msdn.microsoft.com/en-us/library/ms534369%28VS.85%29.aspx&#34;&gt;ResponseText&lt;/a&gt;
will be terminated at the first null value, we must use ResponseBody.
This requires a bit of VBScript, which can do one of the following
things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Get the numeric value of each unsigned byte and turn that into a
number in a string of comma delimited numbers. This is less
efficient, but gets you in and out of VBScript as quickly as
possible, allowing a generic base64 encoding routine. This is the
route I followed (it may be less efficient, but it pales in
comparison with the XHR request just made):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Function BinaryArrayToAscCSV( aBytes )
 Dim j, sOutput
        sOutput = &amp;quot;BinaryArrayToAscCSV&amp;quot;
 For j = 1 to LenB(aBytes)
  sOutput= sOutput &amp;amp; AscB( MidB(aBytes,j,1) )
  sOutput= sOutput &amp;amp; &amp;quot;,&amp;quot;
 Next
 BinaryArrayToAscCSV = sOutput
End Function
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Base 64 encode it directly in VBScript.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once this is done, we can then base64 encode it using a fairly generic
function in Javascript:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Base64 = {
 
 // private property
 _keyStr : &amp;quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=&amp;quot;,

 encodeBinaryArrayAsString : function(input){
  var ascArr;
  var output = &amp;quot;&amp;quot;;
  var bytebuffer;
  var encodedCharIndexes = new Array(4);
  
  var inx = 0;
  ascArr = input.substring(&amp;quot;BinaryArrayToAscCSV&amp;quot;.length, input.length - 1).split(&#39;,&#39;);
  while(inx &amp;lt; ascArr.length){
   // Fill byte buffer array
   bytebuffer = new Array(3);
   for(jnx = 0; jnx &amp;lt; bytebuffer.length; jnx++)
    if(inx &amp;lt; ascArr.length)
     bytebuffer[jnx] = parseInt(ascArr[inx++]); 
    else
     bytebuffer[jnx] = 0;
     
   // Get each encoded character, 6 bits at a time
   // index 1: first 6 bits
   encodedCharIndexes[0] = bytebuffer[0] &amp;gt;&amp;gt; 2;  
   // index 2: second 6 bits (2 least significant bits from input byte 1 + 4 most significant bits from byte 2)
   encodedCharIndexes[1] = ((bytebuffer[0] &amp;amp; 0x3) &amp;lt;&amp;lt; 4) | (bytebuffer[1] &amp;gt;&amp;gt; 4);  
   // index 3: third 6 bits (4 least significant bits from input byte 2 + 2 most significant bits from byte 3)
   encodedCharIndexes[2] = ((bytebuffer[1] &amp;amp; 0x0f) &amp;lt;&amp;lt; 2) | (bytebuffer[2] &amp;gt;&amp;gt; 6);  
   // index 3: forth 6 bits (6 least significant bits from input byte 3)
   encodedCharIndexes[3] = bytebuffer[2] &amp;amp; 0x3f;  
   
   // Determine whether padding happened, and adjust accordingly
   paddingBytes = inx - (ascArr.length - 1);
   switch(paddingBytes){
    case 2:
     // Set last 2 characters to padding char
     encodedCharIndexes[3] = 64; 
     encodedCharIndexes[2] = 64; 
     break;
    case 1:
     // Set last character to padding char
     encodedCharIndexes[3] = 64; 
     break;
    default:
     break; // No padding - proceed
   }
   // Now we will grab each appropriate character out of our keystring
   // based on our index array and append it to the output string
   for(jnx = 0; jnx &amp;lt; encodedCharIndexes.length; jnx++)
    output += this._keyStr.charAt(encodedCharIndexes[jnx]);     
  }
  return output;
 }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Firefox:&lt;/p&gt;
&lt;p&gt;Firefox works a little differently, as there is no RequestBody property.
In this case, RequestText is not truncated as long as you override the
mime type coming from the server, forcing Firefox to pass the data
unaltered. All we need to do is compensate for binary data coming back
and being placed in a Unicode Javascript string. To compensate, we can
AND each character with 0xFF to throw away the high-order byte (see
&lt;a href=&#34;https://developer.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data&#34;&gt;https://developer.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data&lt;/a&gt;).
The resulting encoding function looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Base64 = {
 
 // private property
 _keyStr : &amp;quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=&amp;quot;,

 encodeBinary : function(input){
  var output = &amp;quot;&amp;quot;;
  var bytebuffer;
  var encodedCharIndexes = new Array(4);
  var inx = 0;
  var paddingBytes = 0;
   
  while(inx &amp;lt; input.length){
   // Fill byte buffer array
   bytebuffer = new Array(3);
   for(jnx = 0; jnx &amp;lt; bytebuffer.length; jnx++)
    if(inx &amp;lt; input.length)
     bytebuffer[jnx] = input.charCodeAt(inx++) &amp;amp; 0xff; // throw away high-order byte, as documented at: https://developer.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data
    else
     bytebuffer[jnx] = 0;
   
   // Get each encoded character, 6 bits at a time
   // index 1: first 6 bits
   encodedCharIndexes[0] = bytebuffer[0] &amp;gt;&amp;gt; 2;  
   // index 2: second 6 bits (2 least significant bits from input byte 1 + 4 most significant bits from byte 2)
   encodedCharIndexes[1] = ((bytebuffer[0] &amp;amp; 0x3) &amp;lt;&amp;lt; 4) | (bytebuffer[1] &amp;gt;&amp;gt; 4);  
   // index 3: third 6 bits (4 least significant bits from input byte 2 + 2 most significant bits from byte 3)
   encodedCharIndexes[2] = ((bytebuffer[1] &amp;amp; 0x0f) &amp;lt;&amp;lt; 2) | (bytebuffer[2] &amp;gt;&amp;gt; 6);  
   // index 3: forth 6 bits (6 least significant bits from input byte 3)
   encodedCharIndexes[3] = bytebuffer[2] &amp;amp; 0x3f;  
   
   // Determine whether padding happened, and adjust accordingly
   paddingBytes = inx - (input.length - 1);
   switch(paddingBytes){
    case 2:
     // Set last 2 characters to padding char
     encodedCharIndexes[3] = 64; 
     encodedCharIndexes[2] = 64; 
     break;
    case 1:
     // Set last character to padding char
     encodedCharIndexes[3] = 64; 
     break;
    default:
     break; // No padding - proceed
   }
   // Now we will grab each appropriate character out of our keystring
   // based on our index array and append it to the output string
   for(jnx = 0; jnx &amp;lt; encodedCharIndexes.length; jnx++)
    output += this._keyStr.charAt(encodedCharIndexes[jnx]);
  }
  return output;
 };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ideally we&amp;rsquo;d combine these two functions into a single encoding
function, but I&amp;rsquo;ve left them separate for clarity. Note also that these
techniques do not appear to work for Safari, Chrome or Opera. It should
work for IE6 if using the correct ActiveX XHR object, but I was not
supporting IE6. I did a spot check on Safari/Chrome/Opera and they were
not working, but I did not investigate as they were not supported
browsers for my implementation. The actual XHR function I used was:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LoadBinaryResource = function(url) { 
  var req = new XMLHttpRequest();  
  req.open(&#39;GET&#39;, url, false);  

  if (req.overrideMimeType)
    req.overrideMimeType(&#39;text/plain; charset=x-user-defined&#39;);  
  req.send(null);  
  if (req.status != 200) return &#39;&#39;;  
  if (typeof(req.responseBody) !== &#39;undefined&#39;) return BinaryArrayToAscCSV(req.responseBody);
  return req.responseText;  
} 

LoadBinaryResourceAsBase64 = function(url) { 
  var data = LoadBinaryResource(url);
  
  if (data.indexOf(&amp;quot;BinaryArrayToAscCSV&amp;quot;) !== -1)
    return Base64.encodeBinaryArrayAsString(data);
  else
    return Base64.encodeBinary(data);  
}
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>It doesn&#39;t get much better than this</title>
      <link>https://emil.lerch.org/2008-09-25-it-doesnt-get-much-better-than-this/</link>
      <pubDate>Thu, 25 Sep 2008 09:13:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-09-25-it-doesnt-get-much-better-than-this/</guid>
      <description>&lt;p&gt;How&amp;rsquo;s this for a setup message? Kinda hard to comply with&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2008-09-25/2008-09-25-it-doesnt-get-much-better-than-this-2008-09-25_0849.png&#34;&gt;&lt;img src=&#34;./posts/2008-09-25/thumbnails/2008-09-25-it-doesnt-get-much-better-than-this-2008-09-25_0849.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>One more technical analysis link</title>
      <link>https://emil.lerch.org/2008-05-27-one-more-technical-analysis-link/</link>
      <pubDate>Tue, 27 May 2008 08:56:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-05-27-one-more-technical-analysis-link/</guid>
      <description>&lt;p&gt;I bumped across &lt;a href=&#34;http://www.stoxline.com/&#34;&gt;Stoxline&lt;/a&gt; during my &lt;a href=&#34;http://emilsblog.lerch.org/2008/05/excercising-intc-stock-options.html&#34;&gt;recent
foray into the technical analysis
world&lt;/a&gt;.
It provides a good quick summary of some key price points.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Excercising INTC stock options</title>
      <link>https://emil.lerch.org/2008-05-23-excercising-intc-stock-options/</link>
      <pubDate>Fri, 23 May 2008 07:50:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-05-23-excercising-intc-stock-options/</guid>
      <description>&lt;p&gt;So I&amp;rsquo;ve excercised the remaining stock options from Intel that remain
above water. There are still a large number of options that will die
worthless (my original options were priced at $67 back in 2001, current
stock price is $23.52, and there were no splits in between). The next
set of options that even have a shot at becoming in the money are priced
around $26, and with 3 weeks out, it would be surprising if they became
valuable. Here&amp;rsquo;s the quick story of how the excercise went down.&lt;/p&gt;
&lt;p&gt;I excercised in three batches. The first batch I got totally lucky and
sold at $27.98, exactly $0.01 off the 52 week high. Dumb luck. I sold
only half because word on the street was that Q1 would be great, and the
price would keep going up. In retrospect, I think selling half was a
good move, but my big mistake here was not to have sold the most
expensively priced options. Those options at $26 would have actually
been worth something.&lt;/p&gt;
&lt;p&gt;The stock tanked this year, but recently began to gain ground. Oddly, it
picked up momentum based on almost no news (the overall market was also
up). Intel did &lt;a href=&#34;http://www.thestreet.com/s/tech-winners-and-losers-intel/newsanalysis/winnerstech/_msnh/10417027.html?&amp;amp;cm_ven=MSNH&amp;amp;cm_cat=FREE&amp;amp;cm_ite=NA&#34;&gt;get an
upgrade&lt;/a&gt;
that did pretty well for the stock. Due to what seemed to be a short
term movement and the imminent expiration of my options, I dug out some
of my technical analysis knowledge and went to town:&lt;/p&gt;
&lt;p&gt;I watched the following indicators:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.investopedia.com/terms/m/macd.asp&#34;&gt;MACD&lt;/a&gt;: I was looking
pretty short-term (&amp;lt;1&amp;gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.investopedia.com/terms/b/bollingerbands.asp&#34;&gt;Bollinger
Bands&lt;/a&gt;: I
thought this would be a fairly useless indicator, but in fact, it
was just when the stock crossed the upper band that it turned
negative.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.investopedia.com/terms/r/rsi.asp&#34;&gt;RSI&lt;/a&gt;: Fairly useful
indicator in this scenario&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.investopedia.com/terms/s/stochasticoscillator.asp&#34;&gt;Slow Stochastic
Oscillator&lt;/a&gt;:
Fast stoch always seems too touchy for me. Maybe if I were trading
several times a day it would be different, but the slow stoch was
useful&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.investopedia.com/terms/p/parabolicindicator.asp&#34;&gt;Parabolic
SAR&lt;/a&gt;: My
primary source of when the options should be sold.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When the stock hit 25 and retreated a bit to 24.60, I decided to sell my
second batch, a fairly small set of options that were priced in the mid
23&amp;rsquo;s. My primary thinking here was to lock in some of the options since
going up over 5% in one day on no news was just crazy.&lt;/p&gt;
&lt;p&gt;At 25, the MACD, Bollinger Bands and RSI indicators all went bearish, so
I thought I&amp;rsquo;d sell when the price started to dip. The next day it opened
pretty low and then dropped below 24.13, which was the Parabolic SAR
trigger point, so now all the indicators I was tracking went bearish. I
sold at 24.08, and so far, it&amp;rsquo;s down another 0.85 from there.&lt;/p&gt;
&lt;p&gt;Lessons learned? The indicators can work&amp;hellip;don&amp;rsquo;t rely on just one, and
make sure that you use the right indicator for your goal.&lt;/p&gt;
&lt;p&gt;Another couple links of use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://research.scottrade.com/public/stocks/charts/charts.asp?symbol=INTC&#34;&gt;Scottrade
chart&lt;/a&gt;:
This chart is continuously updated, and summarizes what the
indicators are saying (e.g. Parabolic Indicator is bearish/MACD is
bullish). The conintuous updates made it more useful than Yahoo. It
does, however, force you to re-add the indicators - no deep link is
possible.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://finance.yahoo.com/echarts?s=INTC#chart1:symbol=intc;range=1m;indicator=bollinger+psar+rsi+macd+stochasticfast;charttype=candlestick;crosshair=on;ohlcvalues=0;logscale=off;source=undefined&#34;&gt;Yahoo
chart&lt;/a&gt;:
I was originally using this chart from Yahoo. Since the URL includes
all the options, it looks exactly as I was using. However, it only
updates at the end of each day.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.tdameritrade.com/tradingtools/commandcenter.html&#34;&gt;TD Ameritrade Command Center
2.0&lt;/a&gt;: A
Java app available for &lt;a href=&#34;http://www.tdameritrade.com/welcome1.html&#34;&gt;TD
Ameritrade&lt;/a&gt; customers, it
let me watch the stock in real-time during the day.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>It could have been so much more effective with a little B&amp;E</title>
      <link>https://emil.lerch.org/2008-05-21-it-could-have-been-so-much-more-effective-with-a-little-b-e/</link>
      <pubDate>Wed, 21 May 2008 07:13:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-05-21-it-could-have-been-so-much-more-effective-with-a-little-b-e/</guid>
      <description>&lt;p&gt;A couple days ago I got a flyer on my door advertising a security system
and monitoring package. I&amp;rsquo;m not sure how many people actually respond to
a blanket flyer like that, but it got me thinking&amp;hellip;wouldn&amp;rsquo;t the flyer
have been more effective had they managed to leave it somewhere &lt;!-- raw HTML omitted --&gt;inside&lt;!-- raw HTML omitted --&gt; the house while we weren&amp;rsquo;t
home? Illegal - yes. Effective: definitely.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Check for valid stored procedures</title>
      <link>https://emil.lerch.org/2008-05-02-check-for-valid-stored-procedures/</link>
      <pubDate>Fri, 02 May 2008 09:23:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-05-02-check-for-valid-stored-procedures/</guid>
      <description>&lt;p&gt;I just posted a &lt;a href=&#34;http://www.codeproject.com/KB/database/validate-sqlserver-proc.aspx&#34;&gt;small
utility&lt;/a&gt;
on &lt;a href=&#34;http://www.codeproject.com&#34;&gt;CodeProject&lt;/a&gt; to check for valid stored
procedures, views, and functions (in SQL Server). It&amp;rsquo;s actually a polish
of some work someone else had done earlier, but if you have a large
number of objects and are doing significant database refactoring, you
may want to check it out.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Street signs</title>
      <link>https://emil.lerch.org/2008-05-01-street-signs/</link>
      <pubDate>Thu, 01 May 2008 08:10:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-05-01-street-signs/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;./posts/2008-05-01/2008-05-01-street-signs-walk.jpg&#34;&gt;&lt;img src=&#34;./posts/2008-05-01/thumbnails/2008-05-01-street-signs-walk.jpg&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When crossing the street and seeing one of the standard walk signs (such
as the one to the left), I have a habit of imitating the sign and saying
&amp;ldquo;white man walking&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Well, Sweden is now crossing the gender barrier with their &lt;a href=&#34;http://www.npr.org/templates/story/story.php?storyId=90090667&#34;&gt;new
signs&lt;/a&gt;.
Where this will lead, I&amp;rsquo;m not sure. Non-caucasians? Dogs? Cats? It does
make me wonder, if the signs are specific, is Sweden now implying that
where this sign is posted, only women are allowed to walk?&lt;br&gt;
&lt;a href=&#34;./posts/2008-05-01/2008-05-01-street-signs-garmanfru.jpg&#34;&gt;&lt;img src=&#34;./posts/2008-05-01/thumbnails/2008-05-01-street-signs-garmanfru.jpg&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Google Maps update</title>
      <link>https://emil.lerch.org/2008-04-17-google-maps-update/</link>
      <pubDate>Thu, 17 Apr 2008 07:06:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-04-17-google-maps-update/</guid>
      <description>&lt;p&gt;I just noticed that &lt;a href=&#34;http://maps.google.com&#34;&gt;Google Maps&lt;/a&gt; introduced the
ability to show historical &amp;ldquo;average traffic&amp;rdquo; for any given time on
particular day of the week. I just used it to confirm my commuting
schedule, and it seems to correlate well with my experiences and
validate that the times I drive are pretty reasonable.&lt;/p&gt;
&lt;p&gt;There are definitely some systemic traffic slowdowns in the Portland
area. For example, 26 west bound between &lt;a href=&#34;http://maps.google.com/?ie=UTF8&amp;amp;layer=t&amp;amp;ll=45.5243,-122.816548&amp;amp;spn=0.021047,0.057335&amp;amp;z=15&#34;&gt;Murray and
Bethany&lt;/a&gt;
is nearly always a problem from 4PM-7PM, probably stemming from the fact
that the road moves from 3 lanes down to 2 just after the Bethany exit.
This is especially unfortunate since the third lane was just
added&amp;hellip;although I do admit that there are much more serious traffic
concerns in the area to focus our resources on.&lt;/p&gt;
&lt;p&gt;It would be nice to know that those that plan road improvements take a
look at this type of data to prioritize their efforts.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Calendar sync bliss (finally)</title>
      <link>https://emil.lerch.org/2008-04-02-calendar-sync-bliss-finally/</link>
      <pubDate>Wed, 02 Apr 2008 19:56:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-04-02-calendar-sync-bliss-finally/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;./posts/2008-04-02/2008-04-02-calendar-sync-bliss-finally-Calendar+sync.png&#34;&gt;&lt;img src=&#34;./posts/2008-04-02/thumbnails/2008-04-02-calendar-sync-bliss-finally-Calendar+sync.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;br&gt;
Finally, a calendar sync solution that works. After my experiment with
&lt;a href=&#34;http://www.google.com/url?sa=t&amp;amp;ct=res&amp;amp;cd=1&amp;amp;url=http%3A%2F%2Fwww.daveswebsite.com%2Fsoftware%2Fgsync%2F&amp;amp;ei=z-f0R6zgNoiYoQS90ZC_DQ&amp;amp;usg=AFQjCNH3xj_OGBn4h2o4CMxFzTU8Px_TKQ&amp;amp;sig2=lSOehs0id7vBbq2inRdnNQ&#34;&gt;gSyncIT&lt;/a&gt;,
I just couldn&amp;rsquo;t quite get past some of its limitations. Once multiple
calendars get involved, things get a bit ugly.&lt;/p&gt;
&lt;p&gt;A coworker recommended
&lt;a href=&#34;http://www.google.com/url?sa=t&amp;amp;ct=res&amp;amp;cd=1&amp;amp;url=http%3A%2F%2Fwww.syncmycal.com%2F&amp;amp;ei=Ruj0R7T5EYiYoQSX0Yy_DQ&amp;amp;usg=AFQjCNEmiPLtCDyy3CMY1BHS0iRFzi2pGQ&amp;amp;sig2=jmJ4PsN07peTa6JE9sSXVw&#34;&gt;SyncMyCal&lt;/a&gt;
as a way past those issues, and I&amp;rsquo;m pretty happy. You&amp;rsquo;ll see my
calendaring setup to the right.&lt;/p&gt;
&lt;p&gt;Google Calendar is pretty much my primary calendar, and I have several
calendars loaded. Some calendars, like US, UK, Indian Holidays and the
Winterhawks game schedule are information only. However, I have my own
calendar, a family calendar for events the whole family is attending,
and two TripIt calendars (one for my wife, one for me) that do a great
job representing any travel I&amp;rsquo;m doing.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like to make sure that these calendars are available (at least in
free/busy form) to other Exchange users at work, and that the calendars
get posted to my cell phone using Windows Mobile. The Outlook 2007
Internet Calendars feature doesn&amp;rsquo;t allow this, so I needed a solution
that would allow my Google calendars to merge into my single Outlook
calendar. I was also looking for my work calendar to post out to Google
so I could have visibility there as well.&lt;/p&gt;
&lt;p&gt;SyncMyCal had the answer through the use of category synchronization.
Each Google calendar has their events assigned to a specific category,
and only those events are sync&amp;rsquo;d with that calendar. Anything not
assigned to a category (work stuff) gets sync&amp;rsquo;d to Google under a new
&amp;ldquo;Work&amp;rdquo; calendar. I didn&amp;rsquo;t need full synchronization here, but it&amp;rsquo;s nice
to know I have it if I need it.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s missing? I can&amp;rsquo;t find a way to have SyncMyCal make all
Google-sourced events private on the Outlook side (although it does sync
the private flag). I have to remember to mark anything truly private as
such. Not a big deal, but it&amp;rsquo;s worth mentioning.&lt;/p&gt;
&lt;p&gt;There is definitely whitespace for an entrepreneur to simplify
calendaring&amp;hellip;I can&amp;rsquo;t believe that my needs are *THAT* special&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>C# Covariant Generics</title>
      <link>https://emil.lerch.org/2008-04-01-c-covariant-generics/</link>
      <pubDate>Tue, 01 Apr 2008 08:28:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-04-01-c-covariant-generics/</guid>
      <description>&lt;p&gt;Sorry for the highly technical posts&amp;hellip;lighter stuff to come!&lt;/p&gt;
&lt;p&gt;This has really been bugging me on my current project. In an object
oriented system, if one class inherits from another (e.g. &amp;ldquo;Cat&amp;rdquo; inherits
from &amp;ldquo;Animal&amp;rdquo;), and a method expects to receive the base class as a
parameter, it is safe to send in a subtype. In my example, a method
operating on &amp;ldquo;Animal&amp;rdquo; can take &amp;ldquo;Cat&amp;rdquo; safely. This is sometimes called
&amp;ldquo;downcasting&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;This behavior should extend to generic types. Again, in my example, if a
method expects a List of Animals, I should be able to pass in a List of
Cats. However, C# does not allow this behavior (but the CLR does).
Bah!&lt;/p&gt;
&lt;p&gt;Here is some more background I&amp;rsquo;ve dug up on the issue:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://research.microsoft.com/research/pubs/view.aspx?type=inproceedings&amp;amp;id=1215&#34;&gt;MS Research
article&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://groups.google.com/group/microsoft.public.dotnet.languages.csharp/browse_thread/thread/b47879b2fecdf61a&#34;&gt;Google Groups
discussion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Yet more SQL 2005 fun: Developing search procs</title>
      <link>https://emil.lerch.org/2008-03-27-yet-more-sql-2005-fun-developing-search-procs/</link>
      <pubDate>Thu, 27 Mar 2008 08:47:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-27-yet-more-sql-2005-fun-developing-search-procs/</guid>
      <description>&lt;p&gt;A common pattern in applications is to have a search stored procedure
work as the backend for a search screen. If a term has not been passed
by the user, the intent is that there is no filter on the results for
that parameter. Here&amp;rsquo;s a &lt;a href=&#34;http://www.sommarskog.se/dyn-search.html&#34;&gt;great
article&lt;/a&gt; discussing the ways
that this can be acheived. I&amp;rsquo;m a personal fan of the COALESCE statement
talked about in the
&lt;a href=&#34;http://www.sommarskog.se/dyn-search.html#static&#34;&gt;static&lt;/a&gt; discussion.&lt;/p&gt;
&lt;p&gt;One area that was not discussed in the article was the idea of having
similar functionality with a list of filters (e.g. filter to these
specific books in the library). I found a &lt;a href=&#34;http://weblogs.asp.net/jgalloway/archive/2007/02/16/passing-lists-to-sql-server-2005-with-xml-parameters.aspx&#34;&gt;pretty good
article&lt;/a&gt;
talking about SQL 2005&amp;rsquo;s use of Xml parameters to do this - Xml&amp;rsquo;s main
benefit being that it&amp;rsquo;s more designed for this use case than a delimited
string. However, the same problem now applies&amp;hellip;what if I either have a
list of books, or don&amp;rsquo;t pass anything, meaning I want to search across
all books?&lt;/p&gt;
&lt;p&gt;My solution was to grab all the data from the reference table and build
my own Xml string. Then I could use it in the main select statement:&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;IF &lt;!-- raw HTML omitted --&gt;@param&lt;!-- raw HTML omitted --&gt; is null &lt;!-- raw HTML omitted --&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;SET &lt;!-- raw HTML omitted --&gt;@param =&lt;br&gt;
(&lt;!-- raw HTML omitted --&gt;SELECT &lt;!-- raw HTML omitted --&gt;1 &lt;!-- raw HTML omitted --&gt;AS &lt;!-- raw HTML omitted --&gt;Tag,0 &lt;!-- raw HTML omitted --&gt;AS &lt;!-- raw HTML omitted --&gt;Parent,MyElementName &lt;!-- raw HTML omitted --&gt;AS &lt;!-- raw HTML omitted --&gt;[Root!1!string!element]&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;FROM &lt;!-- raw HTML omitted --&gt;sourcetable &lt;!-- raw HTML omitted --&gt;FOR XML EXPLICIT&lt;!-- raw HTML omitted --&gt;)&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;SELECT&lt;!-- raw HTML omitted --&gt;&amp;hellip;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;WHERE&lt;!-- raw HTML omitted --&gt;&amp;hellip;&lt;br&gt;
&amp;hellip;&lt;!-- raw HTML omitted --&gt;AND &lt;!-- raw HTML omitted --&gt;&lt;br&gt;
FieldName &lt;!-- raw HTML omitted --&gt;IN &lt;!-- raw HTML omitted --&gt;&lt;br&gt;
(&lt;!-- raw HTML omitted --&gt;SELECT &lt;!-- raw HTML omitted --&gt;Field.value(&lt;!-- raw HTML omitted --&gt;&amp;rsquo;.&amp;rsquo;&lt;!-- raw HTML omitted --&gt;,&lt;!-- raw HTML omitted --&gt;&amp;lsquo;VARCHAR(max)&amp;rsquo;&lt;!-- raw HTML omitted --&gt;) FinalName&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;FROM &lt;!-- raw HTML omitted --&gt;@param.nodes(&lt;!-- raw HTML omitted --&gt;&amp;rsquo;//string&amp;rsquo;&lt;!-- raw HTML omitted --&gt;) &lt;!-- raw HTML omitted --&gt;AS &lt;!-- raw HTML omitted --&gt;Table(Field))&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m sure this wouldn&amp;rsquo;t perform well with large tables, but my reference
tables are fairly small (&amp;lt;100 rows each), and it works great.&lt;/p&gt;
&lt;p&gt;FYI: Here is an example COALESCE statement for the standard filters:&lt;/p&gt;
&lt;p&gt;table &lt;!-- raw HTML omitted --&gt;LIKE &lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;&amp;rsquo;%&amp;rsquo;&lt;!-- raw HTML omitted --&gt; + &lt;!-- raw HTML omitted --&gt;COALESCE&lt;!-- raw HTML omitted --&gt;(@Name,
fac.FacilityName ) + &lt;!-- raw HTML omitted --&gt;&amp;rsquo;%&amp;rsquo;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>More SQL 2005: SqlDependency update</title>
      <link>https://emil.lerch.org/2008-03-27-more-sql-2005-sqldependency-update/</link>
      <pubDate>Thu, 27 Mar 2008 08:24:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-27-more-sql-2005-sqldependency-update/</guid>
      <description>&lt;p&gt;While SQL Server data update notifications through the SqlDependency
object seemed like a great idea, I now believe the architecture is
fundamentally flawed&amp;hellip;I&amp;rsquo;m looking forward to changes in this area to
make the feature more robust in the next release(s) of SQL Server and/or
the .NET Framework. Here are some of the problems I&amp;rsquo;ve run into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.codeproject.com/KB/database/SqlDependencyPermissions.aspx&#34;&gt;Massive
amounts&lt;/a&gt;
of &lt;a href=&#34;http://emilsblog.lerch.org/2008/03/database-permissions-for-sqldependency.html&#34;&gt;required
permissions&lt;/a&gt;
in the default run mode&lt;/li&gt;
&lt;li&gt;Query types available are &lt;a href=&#34;http://msdn2.microsoft.com/en-us/library/aewzkxxh.aspx&#34;&gt;incredibly
restrictive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://blogs.msdn.com/remusrusanu/archive/2007/10/12/when-it-rains-it-pours.aspx&#34;&gt;Ineffective tear down of
resources&lt;/a&gt;,
again, in default run mode, with no programatic workaround. (The
worst part? The problem is actually most acute while doing active
development&amp;hellip;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://blogs.msdn.com/remusrusanu/archive/2007/10/12/when-it-rains-it-pours.aspx&#34;&gt;Severe
ramifications&lt;/a&gt;
for any issue, whether caused by environment or poor coding&lt;/li&gt;
&lt;li&gt;Complex (and maybe unusable) &lt;a href=&#34;http://www.sqlskills.com/blogs/bobb/2006/06/28/PreprovisioningAndSqlDependency.aspx&#34;&gt;setup for custom service/queue
implementation&lt;/a&gt;
to alleviate the first two issues. I never really even figured out
that setup when operating in a non-default schema in a restricted
permission environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This feature is a great concept, but with an implementation that
severely limits its use. My recommendation is to avoid this
functionality in nearly all cases. In my current project, I&amp;rsquo;ve moved to
a custom cache implementation that checks last modified date across all
all lookup tables and clears the cache of all out of date lists. If
something happens in between checks, we&amp;rsquo;ll just have to deal with that.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Database permissions for SQLDependency</title>
      <link>https://emil.lerch.org/2008-03-20-database-permissions-for-sqldependency/</link>
      <pubDate>Thu, 20 Mar 2008 08:35:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-20-database-permissions-for-sqldependency/</guid>
      <description>&lt;p&gt;While I like the idea of &lt;a href=&#34;http://www.google.com/url?sa=t&amp;amp;ct=res&amp;amp;cd=2&amp;amp;url=http%3A%2F%2Fmsdn2.microsoft.com%2Fen-us%2Flibrary%2Fms130764.aspx&amp;amp;ei=E4XiR9XkL4rAgwPD0fXLAQ&amp;amp;usg=AFQjCNFVAgsQ5pURZzpx_aiqE163Seci0w&amp;amp;sig2=QJL4QWmeS-YWhunMenehfw&#34;&gt;SQL 2005 Query
notifications&lt;/a&gt;,
the setup restrictions and instructions are fairly opaque. Blah! I did
manage to get it working after noting all the &lt;a href=&#34;http://msdn2.microsoft.com/en-us/library/aewzkxxh.aspx&#34;&gt;restrictions on the
query&lt;/a&gt; in this
MSDN article, but then I made the mistake of removing dbo permissions
from the user, and was thrown into the mix again for another hour of
churning.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://blogs.msdn.com/dataaccess/archive/2005/09/27/474447.aspx&#34;&gt;This blog
post&lt;/a&gt;
was pretty useful, but didn&amp;rsquo;t go quite all the way. Later, I found &lt;a href=&#34;http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=533779&amp;amp;SiteID=1&#34;&gt;a
post describing more
details&lt;/a&gt;
after some searching, and came up with this set of grant statements to
make it work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GRANT ALTER ON SCHEMA :: [schemaname] TO [Role]&lt;/li&gt;
&lt;li&gt;GRANT CREATE PROCEDURE TO [Role]&lt;/li&gt;
&lt;li&gt;GRANT CREATE QUEUE TO [Role]&lt;/li&gt;
&lt;li&gt;GRANT CREATE SERVICE TO [Role]&lt;/li&gt;
&lt;li&gt;GRANT REFERENCES on
CONTRACT::[http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification]
to [Role]&lt;/li&gt;
&lt;li&gt;GRANT VIEW DEFINITION TO [Role]&lt;/li&gt;
&lt;li&gt;GRANT SELECT to [Role]&lt;/li&gt;
&lt;li&gt;GRANT SUBSCRIBE QUERY NOTIFICATIONS TO [Role]&lt;/li&gt;
&lt;li&gt;GRANT RECEIVE ON QueryNotificationErrorsQueue TO [Role]&lt;/li&gt;
&lt;li&gt;GRANT IMPERSONATE ON USER::DBO TO [Role]&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Windsor Solutions</title>
      <link>https://emil.lerch.org/2008-03-18-windsor-solutions/</link>
      <pubDate>Tue, 18 Mar 2008 08:20:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-18-windsor-solutions/</guid>
      <description>&lt;p&gt;After 7 years at &lt;a href=&#34;http://www.intel.com&#34;&gt;Intel&lt;/a&gt;, I&amp;rsquo;ve have now joined
&lt;a href=&#34;http://www.windsorsolutions.com&#34;&gt;Windsor Solutions&lt;/a&gt;, a 35 employee
company focused on systems to manage environmental reporting and
compliance at various state and local governments. It&amp;rsquo;s energizing to be
part of such a vibrant, growing organization, with the ability to wear
many hats!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Javascript/PHP annoyances</title>
      <link>https://emil.lerch.org/2008-03-12-javascript-php-annoyances/</link>
      <pubDate>Wed, 12 Mar 2008 08:21:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-12-javascript-php-annoyances/</guid>
      <description>&lt;p&gt;I ran across a problem the other day in a PHP script, where a variable
wasn&amp;rsquo;t getting populated. It turned out that I misspelled the variable
name - clearly my fault. However, it got me thinking about the language
itself, and how it is actually somewhat hypocriful. On one hand, PHP
(and Javascript for that matter), is relaxed when it comes to variables.
There is no strong typing, and in PHP, you don&amp;rsquo;t even need to declare a
variable before using it. On the other hand, both languages are type
sensitive.&lt;/p&gt;
&lt;p&gt;It seems to me that if you&amp;rsquo;re going to have a laid back policy in
regards to variable naming, you should also be laid back when it comes
to case. Being case sensitive with weak controls on variable definition
doesn&amp;rsquo;t make sense. It&amp;rsquo;s the computer science equivelent of mixing first
and third person.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Add-in manager not disabling add-ins in Visual Studio?</title>
      <link>https://emil.lerch.org/2008-03-07-add-in-manager-not-disabling-add-ins-in-visual-studio/</link>
      <pubDate>Fri, 07 Mar 2008 11:15:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-07-add-in-manager-not-disabling-add-ins-in-visual-studio/</guid>
      <description>&lt;p&gt;I stumbled across this problem today, and found an interesting feedback
page to Microsoft regarding the issue:
&lt;a href=&#34;http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=105560&#34;&gt;http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=105560&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Workaround:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Regedit:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\AddIns&lt;/li&gt;
&lt;li&gt;Go into the add-in key you&amp;rsquo;re interested in disabling&lt;/li&gt;
&lt;li&gt;Manually set the LoadBehavior entry to 0x0&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Google Calendar Sync</title>
      <link>https://emil.lerch.org/2008-03-06-google-calendar-sync/</link>
      <pubDate>Thu, 06 Mar 2008 10:06:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-06-google-calendar-sync/</guid>
      <description>&lt;p&gt;Looks like Google just released a &lt;a href=&#34;http://www.google.com/support/calendar/bin/answer.py?answer=89955&#34;&gt;tool to synchronize Google Calendars
with
Outlook&lt;/a&gt;.
Woo hoo! Unfortunately, it will only sync with your primary calendar, so
in the meantime, I&amp;rsquo;m still using
&lt;a href=&#34;http://www.google.com/url?sa=t&amp;amp;ct=res&amp;amp;cd=1&amp;amp;url=http%3A%2F%2Fwww.daveswebsite.com%2Fsoftware%2Fgsync%2F&amp;amp;ei=MjjQR6b2N4KigQPF2sH6Ag&amp;amp;usg=AFQjCNH3xj_OGBn4h2o4CMxFzTU8Px_TKQ&amp;amp;sig2=FZhFdIeiEzAExQfTmEUZsg&#34;&gt;gsyncit&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Tip of the day using 7-Zip</title>
      <link>https://emil.lerch.org/2008-03-05-tip-of-the-day-using-7-zip/</link>
      <pubDate>Wed, 05 Mar 2008 07:28:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-05-tip-of-the-day-using-7-zip/</guid>
      <description>&lt;p&gt;I just stumbled across some interesting behavior in 7-Zip. Not sure why
this is how it works, but if you drag and drop a file from 7-Zip into a
directory to extract, the extract will first go to the temporary
directory and then be copied over into the destination file. If you
click the &amp;ldquo;Extract&amp;rdquo; button and select the directory, the file is
extracted directly in the destination directory.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>LCD vs. Plasma</title>
      <link>https://emil.lerch.org/2008-03-04-lcd-vs-plasma/</link>
      <pubDate>Tue, 04 Mar 2008 08:20:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-04-lcd-vs-plasma/</guid>
      <description></description>
    </item>
    
    <item>
      <title>FIOS TV</title>
      <link>https://emil.lerch.org/2008-03-04-fios-tv/</link>
      <pubDate>Tue, 04 Mar 2008 08:19:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-04-fios-tv/</guid>
      <description></description>
    </item>
    
    <item>
      <title>Jabra BT8010</title>
      <link>https://emil.lerch.org/2008-03-04-jabra-bt8010/</link>
      <pubDate>Tue, 04 Mar 2008 08:19:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-04-jabra-bt8010/</guid>
      <description></description>
    </item>
    
    <item>
      <title>Logitech MX 5000/Bluetooth</title>
      <link>https://emil.lerch.org/2008-03-04-logitech-mx-5000-bluetooth/</link>
      <pubDate>Tue, 04 Mar 2008 08:19:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-04-logitech-mx-5000-bluetooth/</guid>
      <description></description>
    </item>
    
    <item>
      <title>Uniball 207</title>
      <link>https://emil.lerch.org/2008-03-04-uniball-207/</link>
      <pubDate>Tue, 04 Mar 2008 08:09:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2008-03-04-uniball-207/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;./posts/2008-03-04/2008-03-04-uniball-207-Uniball207.jpg&#34;&gt;&lt;img src=&#34;./posts/2008-03-04/thumbnails/2008-03-04-uniball-207-Uniball207.jpg&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;br&gt;
I had a little pen issue - a few of my good pens were wearing out. Bad
pens kind of bother me. I&amp;rsquo;m too cheap to spend a lot of money on pens. I
did a quick search on the Internet, came across &lt;a href=&#34;http://www.google.com/url?sa=t&amp;amp;ct=res&amp;amp;cd=2&amp;amp;url=http%3A%2F%2Fwww.moleskinerie.com%2F2006%2F06%2Fpen_review_unib.html&amp;amp;ei=Y3TNR_mlLIqmpASn4Jz6Dw&amp;amp;usg=AFQjCNGpdn4gtLXgHNFFafCf2HdYWA88vA&amp;amp;sig2=WYOUzwwaUAwsN5nS-GyTsQ&#34;&gt;this
review&lt;/a&gt;,
and was sold. Costco carries a &lt;a href=&#34;http://www.costco.com/Browse/Productgroup.aspx?Prodid=11275103&amp;amp;search=207&amp;amp;Mo=2&amp;amp;cm_re=1_en-_-Top_Left_Nav-_-Top_search&amp;amp;lang=en-US&amp;amp;Nr=P_CatalogName:BC&amp;amp;Sp=S&amp;amp;N=5000043&amp;amp;whse=BC&amp;amp;Dx=mode+matchallpartial&amp;amp;Ntk=Text_Search&amp;amp;Dr=P_CatalogName:BC&amp;amp;Ne=4000000&amp;amp;D=207&amp;amp;Ntt=207&amp;amp;No=0&amp;amp;Ntx=mode+matchallpartial&amp;amp;Nty=1&amp;amp;topnav=&amp;amp;s=1&#34;&gt;pack of 12 for
$15.68&lt;/a&gt;
online (I thought they were 11.59 in the store, though). Done deal -
after about a month, I&amp;rsquo;m still &lt;!-- raw HTML omitted --&gt;very&lt;!-- raw HTML omitted --&gt; happy with the choice.&lt;/p&gt;
&lt;p&gt;(Note: I do not participate in paid review stuff&amp;hellip;my opinions are 100%
uninfluenced).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>...And, we&#39;re back!</title>
      <link>https://emil.lerch.org/2008-02-29-and-were-back/</link>
      <pubDate>Fri, 29 Feb 2008 15:59:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2008-02-29-and-were-back/</guid>
      <description>&lt;p&gt;What a crazy few months - more on that later. Just wanted to mention
that I do intend to pick up the blog again (roughly daily as I did last
year). Future topics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Work-related stuff&lt;/li&gt;
&lt;li&gt;Javascript/PHP hypocrisy&lt;/li&gt;
&lt;li&gt;Consumer electronics (FIOS TV service, Logitech MX 5000 for BT,
Jabra BT8010, etc.)&lt;/li&gt;
&lt;li&gt;Work done to the house&lt;/li&gt;
&lt;li&gt;Other stuff on my mind that no one probably cares about&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also wanted to quickly mention a big CONGRATULATIONS to Doug and Lisa
Downey on the birth of their daughter Greta Marie!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Standards...or lack thereof</title>
      <link>https://emil.lerch.org/2007-08-29-standards-or-lack-thereof/</link>
      <pubDate>Wed, 29 Aug 2007 15:42:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-08-29-standards-or-lack-thereof/</guid>
      <description>&lt;p&gt;I was just typing up an email, and noticed that I looked down at the
keyboard to find the delete key. I asked myself why, and then looked at
the keyboards around me. Home, End, Delete, Insert, page up and page
down are all grouped together, but on every keyboard I use, they&amp;rsquo;re in a
different configuration.&lt;/p&gt;
&lt;p&gt;My Dell keyboard at work is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;Home PgUp&lt;!-- raw HTML omitted --&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;End PgDn&lt;!-- raw HTML omitted --&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;Delete Insert&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My Thinkpad:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;Insert Home PgUp&lt;!-- raw HTML omitted --&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;Delete End PgDwn&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My &lt;a href=&#34;http://www.logitech.com/index.cfm/keyboards/keyboard_mice_combos/devices/140&amp;amp;cl=us,en&#34;&gt;new
keyboard&lt;/a&gt;
at home:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;Home End&lt;!-- raw HTML omitted --&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;Insert PgUp&lt;!-- raw HTML omitted --&gt;&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;Delete PgDwn&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;All three configurations make sense (especially the Thinkpad, which has
an additional complexity of space issues), but it seems that at least on
the desktop, there should be a standard.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Determining internal vs. external email</title>
      <link>https://emil.lerch.org/2007-08-26-determining-internal-vs-external-email/</link>
      <pubDate>Sun, 26 Aug 2007 18:57:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-08-26-determining-internal-vs-external-email/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been trying to get a good Outlook filter to determine internal from
external mail. You&amp;rsquo;d almost think this is a standard request, but it&amp;rsquo;s
actually pretty difficult, especially at a large organization like
Intel. After some time trying to figure it out and following some &lt;a href=&#34;http://office.microsoft.com/en-us/outlook/HA011502011033.aspx&#34;&gt;bad
advice from
Microsoft&lt;/a&gt;,
I ran across &lt;a href=&#34;http://blogs.geekdojo.net/jez/archive/2004/04/27/1782.aspx&#34;&gt;Ray Jezek&amp;rsquo;s Blog : Outlook Rules with
Exchange&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The idea is fairly sound, but alas, does not work at Intel, where we
have many Exchange servers. When a message moves from one server to
another, the message gets Internet headers, so the idea of filtering a
message header for the word &amp;ldquo;Received&amp;rdquo; doesn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;I tried other variations on this theme, though, and finally came up with
the solution. My rule now matches any message with a message header
containing the text of one of the servers listed in &lt;a href=&#34;http://www.zoneedit.com/lookup.html?host=intel.com&amp;amp;type=MX&amp;amp;server=&amp;amp;forward=Look+it+up&#34;&gt;Intel.com&amp;rsquo;s MX
records&lt;/a&gt;.
Internally, our mail will not use one of these hosts, so that&amp;rsquo;s the only
sure-fire way to catch external, and only external email that I&amp;rsquo;ve
found. I suppose if the sender listed the IP address of the mail host my
rule would fail, but that&amp;rsquo;s a risk I&amp;rsquo;m willing to take. ;-)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Slowdown in Visual Studio</title>
      <link>https://emil.lerch.org/2007-08-15-slowdown-in-visual-studio/</link>
      <pubDate>Wed, 15 Aug 2007 16:24:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-08-15-slowdown-in-visual-studio/</guid>
      <description>&lt;p&gt;The other day I had a sudden and mysterious slowdown in compile times
within Visual Studio. When I didn&amp;rsquo;t see an associated CPU spike during
the compile, I increased the verbosity of the compiler and checked the
output, only to see it pausing at a file copy.&lt;/p&gt;
&lt;p&gt;Here I had added my project&amp;rsquo;s SQL Express database to a library
directory rather than the web site, so studio dutifully copied the
database over. All this was fine, except that during development, I bulk
loaded a ton of data, and the file was now about 240MB.&lt;/p&gt;
&lt;p&gt;Note to self&amp;hellip;don&amp;rsquo;t copy 240MB database files as part of your compile.
;-)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Google reader</title>
      <link>https://emil.lerch.org/2007-08-14-google-reader/</link>
      <pubDate>Tue, 14 Aug 2007 16:19:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-08-14-google-reader/</guid>
      <description>&lt;p&gt;So I&amp;rsquo;m now giving &lt;a href=&#34;http://reader.google.com&#34;&gt;Google Reader&lt;/a&gt; a try, for
the following reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I am going on an extended, paid leave from Intel shortly, and will
likely not be using my notebook much&lt;/li&gt;
&lt;li&gt;As a result of #1, I want to be able to read my feeds anywhere&lt;/li&gt;
&lt;li&gt;I believe Outlook RSS is contributing heavily to random slowdowns&lt;/li&gt;
&lt;li&gt;Outlook RSS is certainly helping Outlook slam my hard disk (this is
a problem in Outlook 2003, seems to have gotten worse in 2007, and
is even crazier with several RSS feeds)&lt;/li&gt;
&lt;li&gt;I don&amp;rsquo;t see duplicate posts in Google reader the way I was in
Outlook. In Outlook it once got so bad that my RSS feeds were
consuming more than 250MB of storage in my account, mostly due to
one 2MB post that it refused to detect as a duplicate.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I tried Google Reader a while ago and wasn&amp;rsquo;t a big fan of the interface,
but now that I&amp;rsquo;m using it a bit more I like it. A big problem for me is
that it defaults to expanded view, which I still don&amp;rsquo;t like. List view
is much better, as I rarely read the items (the trend tools tell me that
over the last few days I&amp;rsquo;ve read 10% of posts in even my favorite
feeds).&lt;/p&gt;
&lt;p&gt;Another problem is offline usage, which was somewhat fixed by &lt;a href=&#34;http://gears.google.com&#34;&gt;Google
Gears&lt;/a&gt;. I don&amp;rsquo;t like the fact that I have to
manually click the icon to download data (apparently this is &lt;a href=&#34;http://blogs.zdnet.com/Berlind/?p=529&#34;&gt;supported
in gears, but not implemented by the reader
developers&lt;/a&gt;&amp;hellip;fix this!). I
suppose I&amp;rsquo;ll have to deal with this, but it&amp;rsquo;s one more thing to remember
if I&amp;rsquo;m taking a flight.&lt;/p&gt;
&lt;p&gt;On the flip side, I love the fact that I can click on a folder and see
all the posts for all the feeds underneath it. I couldn&amp;rsquo;t do this in
Outlook (and only sort of could in some other products I tried), and
resorted to using &lt;a href=&#34;http://pipes.yahoo.com&#34;&gt;Yahoo! Pipes&lt;/a&gt; to combine a
bunch of feeds that weren&amp;rsquo;t updated frequently. The problem is that I
couldn&amp;rsquo;t clearly see the source of the post anymore, since pipes renames
the source to the name of your pipe, and I couldn&amp;rsquo;t figure out how to
undo this.&lt;/p&gt;
&lt;p&gt;The folder aggregation feature allows me the ability to undo the work in
Pipes, and now I can see the sources. Rarely do I click on an actual
feed&amp;hellip;mostly I walk through folder by folder and mark all as read
unless something catches my eye. Oh - and the keyboard shortcuts are
nice too. Eventually I&amp;rsquo;m sure I&amp;rsquo;ll want search capabilities, but
hopefully that will be done before I actually need it. In the meantime,
Google can work on the offline issues. ;-)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Software I install</title>
      <link>https://emil.lerch.org/2007-08-09-software-i-install/</link>
      <pubDate>Thu, 09 Aug 2007 13:49:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-08-09-software-i-install/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been working on &lt;a href=&#34;http://spreadsheets.google.com/pub?key=p9VpnOc6eSLLWVL_yUTWrFg&#34;&gt;this
list&lt;/a&gt;
for some time, and now I think it&amp;rsquo;s finally near comprehensive. This is
the full list of software I install. Software is organized by category,
where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;!-- raw HTML omitted --&gt;Basic &lt;!-- raw HTML omitted --&gt;software gets
installed on any machine&lt;/li&gt;
&lt;li&gt;&lt;!-- raw HTML omitted --&gt;Home &lt;!-- raw HTML omitted --&gt;software gets installed
at home&lt;/li&gt;
&lt;li&gt;&lt;!-- raw HTML omitted --&gt;Development &lt;!-- raw HTML omitted --&gt;software gets
installed on any machine whenever I need it&lt;/li&gt;
&lt;li&gt;&lt;!-- raw HTML omitted --&gt;Business &lt;!-- raw HTML omitted --&gt;software gets
installed on my work laptop&lt;/li&gt;
&lt;li&gt;&lt;!-- raw HTML omitted --&gt;Server &lt;!-- raw HTML omitted --&gt;software is installed
on my home server (expected to be up 24x7)&lt;/li&gt;
&lt;li&gt;&lt;!-- raw HTML omitted --&gt;Portable &lt;!-- raw HTML omitted --&gt;software runs
directly from my USB key&lt;/li&gt;
&lt;li&gt;&lt;!-- raw HTML omitted --&gt;Portable - Development&lt;!-- raw HTML omitted --&gt;
software is used for development from &lt;a href=&#34;http://www.ocztechnology.com/products/flash_drives/ocz_ultra_slim_mini_kart_usb_2_0_flash_drive&#34;&gt;my USB
key&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Make sure to check out the other sheets in the spreadsheet. They
include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;!-- raw HTML omitted --&gt;Resources &lt;!-- raw HTML omitted --&gt;to find good tools&lt;/li&gt;
&lt;li&gt;&lt;!-- raw HTML omitted --&gt;Firefox addins&lt;!-- raw HTML omitted --&gt; (there&amp;rsquo;s no
good way I know of to simply install a large number of addins, which
would be nice for initial install)&lt;/li&gt;
&lt;li&gt;&lt;!-- raw HTML omitted --&gt;Configuration Options&lt;!-- raw HTML omitted --&gt;, which
describes how I configure the various OS and application options (it
would be nice to optimize this). I don&amp;rsquo;t have that list built yet,
but hope to soon.&lt;/li&gt;
&lt;li&gt;Software I&amp;rsquo;ve used in the past and have &lt;!-- raw HTML omitted --&gt;removed &lt;!-- raw HTML omitted --&gt;for one reason or another.
Interesting trip down memory lane for me, but I thought keeping the
list would be good for others who might want to avoid some of the
items on that list&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ve also setup (but not tested) two
&lt;a href=&#34;http://www.installpad.com&#34;&gt;InstallPad&lt;/a&gt; files to help streamline install
of several of these applications. These are items from my
&lt;a href=&#34;http://www.lerch.org/applist.xml&#34;&gt;Basic&lt;/a&gt; category and items from the
&lt;a href=&#34;http://www.lerch.org/homeanddevelopment.xml&#34;&gt;Home and Development&lt;/a&gt;
categories.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll keep the list updated and current, and when I do an initial
install, I&amp;rsquo;m sure the Installpad files will get tested and the list
updated with stuff I&amp;rsquo;ve missed. Here&amp;rsquo;s the URL again for reference:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://spreadsheets.google.com/pub?key=p9VpnOc6eSLLWVL_yUTWrFg&#34;&gt;http://spreadsheets.google.com/pub?key=p9VpnOc6eSLLWVL_yUTWrFg&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The things you learn about yourself</title>
      <link>https://emil.lerch.org/2007-08-08-the-things-you-learn-about-yourself/</link>
      <pubDate>Wed, 08 Aug 2007 09:41:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-08-08-the-things-you-learn-about-yourself/</guid>
      <description>&lt;p&gt;I can&amp;rsquo;t believe it took this long to find out this little factoid about
myself. I was on the phone today with a customer service agent, and I
announced my name as I always do: &amp;ldquo;Hi, this is Emil Lerch. I&amp;rsquo;m calling
because&amp;hellip;&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;In this case, however, after I described the issue I was having, the
customer service agent said, &amp;ldquo;I&amp;rsquo;m sorry, did you say you were with
Merrill Lynch?&amp;rdquo;. Thinking about it, I can totally see how under a bit of
distortion, my name does sound a bit like a giant brokerage firm. Who
knew?&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Really, really cool web site</title>
      <link>https://emil.lerch.org/2007-08-06-really-really-cool-web-site/</link>
      <pubDate>Mon, 06 Aug 2007 16:36:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-08-06-really-really-cool-web-site/</guid>
      <description>&lt;p&gt;Lately I&amp;rsquo;ve been questioning the value of Quicken, since the integration
with financial institutions is finicky and getting everything updated
appropriately takes a really long time. Typically, I use Quicken for 3
purposes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;See our net worth&lt;/li&gt;
&lt;li&gt;See how we spend our money&lt;/li&gt;
&lt;li&gt;Seed tax software with donations and such&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A distant 4th is to see how our investments are allocated and determine
if any rebalancing needs to be done, but the last time I did that, we
just put everything into a spreadsheet.&lt;/p&gt;
&lt;p&gt;In order to accomplish these goals, usually once or twice a year I spend
2-3 days getting everything entered into Quicken and reconciled. I don&amp;rsquo;t
really think we&amp;rsquo;re that complex, but with 2 Roth IRAs, 1 regular IRA,
and investment account, 2 individual bank accounts, 1 joint bank
account, 2 401(k) accounts plus 2 profit sharing accounts, 2 stock
option accounts, 1 529, 2 credit cards, 1 investment account for Kathryn
and a bank account for Kathryn, we have over 18 accounts (there are a
few others&amp;hellip;I thought one time I counted 23, but since then there&amp;rsquo;s
been some consolidation), Introduce dependencies between accounts (e.g.
you should reconcile credit cards before the bank statements), and
things get really messy.&lt;/p&gt;
&lt;p&gt;All this leads to today, where I thought there must be a way to
accomplish my goals faster&amp;hellip;I&amp;rsquo;m a data packrat and it rubs me the wrong
way to abandon the long history of data I have in Quicken, but I really
don&amp;rsquo;t use all that stuff. I did some Google searching and ran across
this &lt;a href=&#34;http://marketplacemoney.publicradio.org/display/web/2006/06/30/virtual_file_cabinet/&#34;&gt;marketplace
interview&lt;/a&gt;
that describes a website called &lt;a href=&#34;https://moneycenter.yodlee.com/&#34;&gt;Yodlee
MoneyCentral&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This site is way, way cool. I set up most of my accounts in about an
hour, mostly because I already have my account login information stored
and synced up in &lt;a href=&#34;http://keepass.info/&#34;&gt;KeePass Password Safe&lt;/a&gt;. It keeps
track of all my financials (mortgage, house pricing manual or through
&lt;a href=&#34;http://www.zillow.com/&#34;&gt;Zillow&lt;/a&gt;, credit cards, bank accounts,
investments) as well as rewards accounts (airlines and such), and
provides automatic transaction download and categorization.&lt;/p&gt;
&lt;p&gt;Other than entering in usernames/passwords (yes, this can be a leap of
faith), the site does the rest, and seconds after setting up the
accounts you can see how your spending is categorized, your total net
worth, portfolio positions, etc. Now that (almost) everything is setup,
I&amp;rsquo;m interested to see the charting available for history&amp;hellip;it lets you
see how your investments, transactions, and net worth change over time,
but it can&amp;rsquo;t get a large amount of historical data on initial setup (I
suspect this is due to the limitations of the financial institutions
more than Yodlee). It also provides email alerts for bills, balances
(high or low), large transactions, securities expiration, airline mile
postings/expirations, etc.&lt;/p&gt;
&lt;p&gt;So, the information for this month is available now, after a one-time
cost of about an hour. Had I tried to see total net worth in Quicken
today, I couldn&amp;rsquo;t, because it would take me about 16 man-hours to bring
it up to date. There is still an account or two that it was having
problems with (I think this is due to recent web site changes at &lt;a href=&#34;http://www.tdameritrade.com/&#34;&gt;TD
Ameritrade&lt;/a&gt;), but already I know my net
worth +/- 10 percent (which would be reduced to 0 if I wanted to login
to TD Ameritrade and add the summary amount to the total on Yodlee).&lt;/p&gt;
&lt;p&gt;Even better, the figure it reported was more than I thought. ;-)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Good (and cheap) computer</title>
      <link>https://emil.lerch.org/2007-08-03-good-and-cheap-computer/</link>
      <pubDate>Fri, 03 Aug 2007 16:28:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-08-03-good-and-cheap-computer/</guid>
      <description>&lt;p&gt;So I&amp;rsquo;ve been running this machine now for the past few weeks, and so
far, it seems great. Most parts were purchased from newegg, and here&amp;rsquo;s
the final list of parts purchased. In all cases, I went for maximizing
price/performance ratio. The case in particular was a great win&amp;hellip;I was
amazed how good it was for the price.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the bill of materials (note that the CPU was purchased from Intel
rather than newegg, and parts that no longer are sold by newegg don&amp;rsquo;t
show up on the wishlist anymore):
&lt;a href=&#34;http://secure.newegg.com/NewVersion/wishlist/PublicWishDetail.asp?WishListNumber=5160671&#34;&gt;http://secure.newegg.com/NewVersion/wishlist/PublicWishDetail.asp?WishListNumber=5160671&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a quick rundown:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rosewill R103A case: $25&lt;/li&gt;
&lt;li&gt;MSI NX8600GT Video card: $112. This is a great card, but actually my
only real mistake. When I ordered the motherboard, I went with a
965P chipset, thinking it had integrated graphics. It did not. The
downside was a little extra cost. The upside is that while I&amp;rsquo;m not a
gamer, this video card will support gaming graphics, and has dual
monitor support as well.&lt;/li&gt;
&lt;li&gt;Gigabyte GA-965P-DS3 motherboard: $120. Looks like they aren&amp;rsquo;t
making it anymore, but it&amp;rsquo;s pretty good&lt;/li&gt;
&lt;li&gt;4GB (2 modules of 2GB each) GSkill memory: $244. This is designed
for dual channel operation, and one of the places I loaded up, as I
run multiple virtual machines on the computer. The project actually
consolidated 2 machines onto 1&amp;hellip;I&amp;rsquo;m probably one of the few
consumers in the world doing server consolidation in their house.&lt;/li&gt;
&lt;li&gt;Scythe CPU cooler: $50. Not needed, as the CPU comes with its own
fan, but this is quiet. Between the fanless video card, quieter hard
drive and this cooler, I can just barely hear the machine.&lt;/li&gt;
&lt;li&gt;Western Digital 120GB SATA hard drive (OEM): $50. Again, not exactly
needed as I had a hard drive in the other machine, but quiet and
fast.&lt;/li&gt;
&lt;li&gt;Core 2 Duo E6600: $123 with Intel employee discount. Here I really
saved some money, but had I not gotten the discount, I would have
gotten the E4500, giving me VT for $146.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reused from one of the old machines was a DVD drive (and
keyboard/mouse/monitor), but had I needed to purchase, I would have
gotten the Lite-on LH-20A1L-05 SATA burner with lightscribe support for
$36.&lt;/p&gt;
&lt;p&gt;This gives me a very nice machine at $724 total cost. Had I not made my
motherboard error, it would have cost $612, and if I were doing this for
someone more normal, it would have cost $526 by adding the DVD burner
and removing 2GB of RAM.&lt;/p&gt;
&lt;p&gt;Interestingly, Windows Vista reports an experience of 5.2, due to the
&lt;!-- raw HTML omitted --&gt;processor&amp;hellip;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How many popups does it take to get your health benefits?</title>
      <link>https://emil.lerch.org/2007-08-02-how-many-popups-does-it-take-to-get-your-health-benefits/</link>
      <pubDate>Thu, 02 Aug 2007 10:59:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-08-02-how-many-popups-does-it-take-to-get-your-health-benefits/</guid>
      <description>&lt;p&gt;I find that internal sites and &amp;ldquo;logistics&amp;rdquo; sites (e.g. travel) tend to
have a crazy number of popups for no reason. Seriously, don&amp;rsquo;t the people
at these companies use the Internet? There is a place an time for a
popup, but these guys go nuts. Here&amp;rsquo;s the popup count for a typical
&amp;ldquo;view my claim status&amp;rdquo; on Intel&amp;rsquo;s (outsourced) health benefits site:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to the home page. Browser reports that a popup was blocked.
Whatever this is, it&amp;rsquo;s irrelevant to what I&amp;rsquo;m doing. I&amp;rsquo;ve &lt;!-- raw HTML omitted --&gt;never&lt;!-- raw HTML omitted --&gt; viewed this popup.&lt;/li&gt;
&lt;li&gt;Click login. A popup window appears to collect my user/password. The
original screen is not used again.&lt;/li&gt;
&lt;li&gt;Click &amp;ldquo;manage my account&amp;rdquo;. Another popup. At least in this case, the
old screen is marginally useful, as I could go back to it to get to
another transaction (like changing my address vs. managing claims,
where I am now). Of course, I&amp;rsquo;ve never gone in to do more than one
thing at a time, so leaving this window is useless to me. I&amp;rsquo;m now at
3 pages open and haven&amp;rsquo;t even gotten to my claim yet.&lt;/li&gt;
&lt;li&gt;Click my account (flexible spending account), and claims, without
popups. Awesome. These are 2 out of 4 mouse clicks on the site that
did &lt;!-- raw HTML omitted --&gt;not&lt;!-- raw HTML omitted --&gt; generate popups.&lt;/li&gt;
&lt;li&gt;Click the claim to see details. Another (blocked) popup, but this
one actually is important and the only popup that I might make an
argument to keep, as it is intended for very detailed information,
has a 2D barcode and is generally printed out, then closed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For those that are counting, I now have 4 windows open, and generally, I
will &lt;!-- raw HTML omitted --&gt;never do anything except print the
last window and close everything&lt;!-- raw HTML omitted --&gt;. My absolute favorite has to be
the popup on clicking the login button&amp;hellip;there are more things wrong
with that than I can even count.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Tip for central air conditioning</title>
      <link>https://emil.lerch.org/2007-08-01-tip-for-central-air-conditioning/</link>
      <pubDate>Wed, 01 Aug 2007 09:57:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-08-01-tip-for-central-air-conditioning/</guid>
      <description>&lt;p&gt;A little bit of randomness. We had an air conditioning tune up
yesterday. Basically, they just checked everything out&amp;hellip;no actual
maintenance was required). During the check-out, they asked if I had a
bunch of vents closed. I had been closing the downstairs vents to force
the cold air upstairs in the summer. However, the idea backfired
somewhat, as this just created back pressure that kept the cold air
sitting in the vents rather than coming through the house.&lt;/p&gt;
&lt;p&gt;The technician told me never to close more than 1 or 2 vents in the
house at any given time. Another AC tip - to reduce the temperature
difference between upstairs and downstairs (without zoning), set the fan
to &amp;ldquo;ON&amp;rdquo; rather than auto. The AC compressor won&amp;rsquo;t kick in unless the
temp gets to high, but leaving the fan on will ensure that the air
continues to get circulated throughout the house.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>When bad decisions collide</title>
      <link>https://emil.lerch.org/2007-07-26-when-bad-decisions-collide/</link>
      <pubDate>Thu, 26 Jul 2007 13:08:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-07-26-when-bad-decisions-collide/</guid>
      <description>&lt;p&gt;Sometimes this stuff drives me nuts&amp;hellip;I get an email with a link, but
the URL itself is attached to a graphical button, and Outlook doesn&amp;rsquo;t
let me copy the hyperlink off an image. I click the button, which
launches my default browser (firefox), which the site doesn&amp;rsquo;t support,
so they redirect me to URL/unsupportedbrowser.aspx in a way that doesn&amp;rsquo;t
allow me to hit the back button to see the original URL.&lt;/p&gt;
&lt;p&gt;In fact, the application, which literally includes a button that adds
you to a list of people attending a meeting, does NOT actually support
firefox. I have to assume the time they spent coding in a nice &amp;ldquo;your
browser is unsupported&amp;rdquo; message far outweighed the time it would have
taken to support a firefox user clicking a single button, but that&amp;rsquo;s
another gripe session. For some reason, this particular app also crashes
firefox when I add the site to the IE Tab Sites filter and let IE run
the app.&lt;/p&gt;
&lt;p&gt;My easiest method for dealing with this situation (without changing
default browsers just for this scenario), is to change my firefox user
agent, let the broken application load, and copy/paste the URL into a
real IE.&lt;/p&gt;
&lt;p&gt;Not until crazy stuff like this is resolved will average users really
pick up firefox. Well, that and getting the browser pre-installed on
machines. ;-)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>D-Link DIR-655 gets certified 802.11n draft status</title>
      <link>https://emil.lerch.org/2007-07-09-d-link-dir-655-gets-certified-802-11n-draft-status/</link>
      <pubDate>Mon, 09 Jul 2007 10:26:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-07-09-d-link-dir-655-gets-certified-802-11n-draft-status/</guid>
      <description>&lt;p&gt;I do like this router, and it&amp;rsquo;s good to see it come through
certification. It seems like we&amp;rsquo;ve been waiting forever for 802.11n
finalization, but things are moving slowly.&lt;/p&gt;
&lt;p&gt;Review: &lt;a href=&#34;http://www.smallnetbuilder.com/content/view/30087/96/&#34;&gt;http://www.smallnetbuilder.com/content/view/30087/96/&lt;/a&gt;&lt;br&gt;
Product Page: &lt;a href=&#34;http://www.dlink.com/products/?pid=530&amp;amp;sec=0&#34;&gt;http://www.dlink.com/products/?pid=530&amp;amp;sec=0&lt;/a&gt;&lt;br&gt;
Certification press release: &lt;a href=&#34;http://www.dlink.com/press/pr/?prid=341&#34;&gt;http://www.dlink.com/press/pr/?prid=341&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>NIH Syndrome: symptom or cause?</title>
      <link>https://emil.lerch.org/2007-07-09-nih-syndrome-symptom-or-cause/</link>
      <pubDate>Mon, 09 Jul 2007 08:20:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-07-09-nih-syndrome-symptom-or-cause/</guid>
      <description>&lt;p&gt;I was listening to a podcast this morning and the person was talking
about &lt;a href=&#34;http://en.wikipedia.org/wiki/Not_Invented_Here&#34;&gt;NIH Syndrome&lt;/a&gt;.
This is one of those things that consistently rubs me the wrong way,
primarily because I believe that managers&amp;rsquo; actions and managers&amp;rsquo; talk
diverge significantly in this area. Many things that are not invented
here cost money. Because, as a coworker once put it, &amp;ldquo;man-hours are
free&amp;rdquo;, the tendency for a lot of managers is to use their people to
re-invent the wheel rather than putting together whatever justification
the organizational bureaucracy needs to cut a PO. Since people are now
re-inventing the wheel, to satisfy their ego they must be able to do it
better. Do this enough times, and it&amp;rsquo;s clear that NIH really is the
symptom of poorly performing management.&lt;/p&gt;
&lt;p&gt;Of course, person-hours are not free, rather they&amp;rsquo;ve already been
negotiated for, while budgeting extra dollars for outside products or
services often involves more administrative work. It&amp;rsquo;s the management
path of least resistance.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Why, oh why</title>
      <link>https://emil.lerch.org/2007-06-22-why-oh-why/</link>
      <pubDate>Fri, 22 Jun 2007 14:23:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-06-22-why-oh-why/</guid>
      <description>&lt;p&gt;Do I get &lt;a href=&#34;https://www22.verizon.com/FiOSForHome/channels/FiOS/olo_futurenotification.aspx?NPN=FN&#34;&gt;this
page&lt;/a&gt;,
telling me that I can&amp;rsquo;t get FiOS, when not only is it possible for me to
get FiOS, but I&amp;rsquo;m &lt;!-- raw HTML omitted --&gt;connecting to the web
site over a FiOS connection!&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;d think they would have fixed this in the past 18 months, but I
guess they&amp;rsquo;re not interested in new customers.&lt;/p&gt;
&lt;p&gt;(I go to the site periodically to find out if I can add TV to my
existing FiOS, but I&amp;rsquo;m not sure why I bother&amp;hellip;I apparently need to call
to find out. When I originally had FiOS installed, the site said I
couldn&amp;rsquo;t get it, but customer service looked in their computers and
found out it was no problem at all)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Smartphone application update</title>
      <link>https://emil.lerch.org/2007-06-20-smartphone-application-update/</link>
      <pubDate>Wed, 20 Jun 2007 17:47:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-06-20-smartphone-application-update/</guid>
      <description>&lt;p&gt;I had some time the other day so I actually looked at the stats for the
&lt;a href=&#34;http://emilsblog.lerch.org/2006/04/smartphone-applications.html&#34;&gt;2 smartphone
applications&lt;/a&gt;
I wrote a while ago. I was shocked to find that
&lt;a href=&#34;http://sourceforge.net/projects/notepadsf&#34;&gt;Notepad&lt;/a&gt; had 558 downloads
and &lt;a href=&#34;http://sourceforge.net/projects/stopwatchplussf&#34;&gt;Stopwatch&lt;/a&gt; had 275
downloads. I don&amp;rsquo;t know how many people are actually installing and
using these applications, but I&amp;rsquo;m glad I could help make their lives a
bit easier.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Excellent illustration of a common business problem</title>
      <link>https://emil.lerch.org/2007-06-18-excellent-illustration-of-a-common-business-problem/</link>
      <pubDate>Mon, 18 Jun 2007 10:08:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-06-18-excellent-illustration-of-a-common-business-problem/</guid>
      <description>&lt;p&gt;Even one presentation like this
&lt;a href=&#34;http://www.youtube.com/watch?v=xn7KaS0UtA8&#34;&gt;video&lt;/a&gt; is too much. Tell me
how you plan to achieve actual business results, and give me some
realistic numbers. The &lt;a href=&#34;http://www.burbed.com/wp-content/uploads/StickGraph2.png&#34;&gt;hockey
stick&lt;/a&gt; doesn&amp;rsquo;t
cut it without significant justification. What really bothers me is when
I&amp;rsquo;m told (especially by my own management), &amp;ldquo;the numbers are just too
hard to get, but there is absolutely a positive ROI to this&amp;rdquo;. Nine times
out of 10 it&amp;rsquo;s a cop out, and the translation is that you&amp;rsquo;re just too
lazy to do the analysis you should be doing.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Home network</title>
      <link>https://emil.lerch.org/2007-06-15-home-network/</link>
      <pubDate>Fri, 15 Jun 2007 13:37:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-06-15-home-network/</guid>
      <description>&lt;p&gt;In addition to all the &lt;a href=&#34;http://emilsblog.lerch.org/2007/06/aaaahhhhh.html&#34;&gt;recent house
changes&lt;/a&gt;, I&amp;rsquo;ve been
reconfiguring the home network to improve a couple things, and it&amp;rsquo;s been
quite the saga. Here&amp;rsquo;s what I wanted to fix:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Move off of &lt;a href=&#34;http://en.wikipedia.org/wiki/WEP&#34;&gt;WEP&lt;/a&gt; security in
favor of &lt;a href=&#34;http://en.wikipedia.org/wiki/WPA2&#34;&gt;WPA2&lt;/a&gt;. This is not just
for better security, but because, at least for &lt;a href=&#34;http://www.dlink.com/products/?pid=530&#34;&gt;my
router&lt;/a&gt;, you cannot use WEP
if you want to support
&lt;a href=&#34;http://en.wikipedia.org/wiki/802.11&#34;&gt;802.11n&lt;/a&gt; clients. I don&amp;rsquo;t have
any yet, but don&amp;rsquo;t want to be prevented from the faster speeds,
either.&lt;/li&gt;
&lt;li&gt;Provide wireless coverage in the kitchen nook. This is the only area
of the house I ever have problems with, and while I don&amp;rsquo;t sit there
and use a computer, anyone we have over tends to do just that.&lt;/li&gt;
&lt;li&gt;Provide a stronger wireless connection for the
&lt;a href=&#34;http://www.tivo.com/&#34;&gt;TiVo&lt;/a&gt;, which would stop the problem of my
mp3&amp;rsquo;s pausing during playback and really, really slow transfers of
shows from computer to TiVo.&lt;/li&gt;
&lt;li&gt;Spend no money&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One slight problem with #4 is that the &lt;a href=&#34;http://www.amazon.com/D-Link-DWL-122-802-11b-Mbps-Adapter/dp/B0000A55BE&#34;&gt;wireless adapter I
use&lt;/a&gt;
is not the official &lt;a href=&#34;http://www.amazon.com/TiVo-Wireless-Network-Adapter-AG0100/dp/B000ER5G6C&#34;&gt;Tivo Wireless
Adapter&lt;/a&gt;
(which didn&amp;rsquo;t exist a couple years ago). TiVo &lt;a href=&#34;http://www.engadget.com/2006/11/06/tivo-raises-rates-limits-wpa-to-own-wifi-adaptor/&#34;&gt;doesn&amp;rsquo;t support WPA in
adapters other than their official
one&lt;/a&gt;,
so it created a problem.&lt;/p&gt;
&lt;p&gt;Well, I had gotten an offer from &lt;a href=&#34;http://www.fon.com/&#34;&gt;Fon&lt;/a&gt; to &amp;ldquo;upgrade&amp;rdquo;
my Fon router to a &lt;a href=&#34;https://shop.fon.com/FonShop/shop/US/ShopController?view=product&amp;amp;product=PRD-001&#34;&gt;La
Fonera&lt;/a&gt;
for free. I originally signed up in order to get the very nice &lt;a href=&#34;http://en.wikipedia.org/wiki/WRT54G&#34;&gt;Linksys
WRT54GL&lt;/a&gt;, and was thinking about
repurposing it once fulling my obligation was over, so this was an
opportunity to repurpose the Linksys early.&lt;/p&gt;
&lt;p&gt;Unfortunately, what I needed from the Linksys was to create a wireless
to wireless bridge. This would connect to the 802.11n/g/b network with
WPA2 as a client (direct wired not an option if I were going to solve
problems 2 and 3). It would also work as an AP broadcasting on 802.11g/b
with WEP to let the TiVo work. I took a look at a &lt;a href=&#34;http://en.wikipedia.org/wiki/Wireless_Distribution_System&#34;&gt;wireless distribution
system&lt;/a&gt; to do
this, but all settings must be shared, and I needed to have different
security.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://dd-wrt.com/&#34;&gt;DD-WRT&lt;/a&gt; did not allow this setup, but I was pointed
out to a &lt;a href=&#34;http://dd-wrt.com/dd-wrtv2/down.php?path=downloads%2Fbeta%2FGENERIC%20BROADCOM%20%28Linksys%2CAsus%20etc.%29%2Fdd-wrt.v24%20beta%2F2007%20-%200516%2F&amp;amp;download=dd-wrt.v24_vpn_wrt54g.bin&#34;&gt;beta version of
v24&lt;/a&gt;
that has this bridging feature, which they call the &lt;a href=&#34;http://www.dd-wrt.com/wiki/index.php/Universal_Wireless_Repeater&#34;&gt;Universal Wireless
Repeater&lt;/a&gt;.
The beta version worked, but in order to get traffic from my main router
to route to the Linksys, I needed a &lt;a href=&#34;http://en.wikipedia.org/wiki/Static_routing&#34;&gt;static
route&lt;/a&gt; to be configured.
This &lt;a href=&#34;ftp://ftp.dlink.com/Gateway/dir655/Firmware/dir655_releasenotes_103.txt&#34;&gt;feature only became
available&lt;/a&gt;
for my DLink router a few weeks before, so I &lt;a href=&#34;http://support.dlink.com/products/view.asp?productid=DIR%2D655&#34;&gt;went through the
upgrade&lt;/a&gt;
and I was finally all set up.&lt;/p&gt;
&lt;p&gt;In testing, I found one more problem. The protocol TiVo uses for
transfer of TV shows (but not anything else) &lt;a href=&#34;http://galleon.tv/component/option,com_joomlaboard/Itemid,26/func,view/catid,30/id,407/&#34;&gt;requires the TiVo to be on
the same subnet as the
server&lt;/a&gt;.
Bummer. Everything else works great, and at this point I&amp;rsquo;m resigned to a
second transfer (server-&amp;gt;Laptop-&amp;gt;TiVo) to accomplish that.&lt;/p&gt;
&lt;p&gt;As an aside, although I love my TiVo, unless something changes, I&amp;rsquo;ll be
forced to move on at some point, based partly on issues like this, and
partly on the lack of HD support without shelling out a ton of money on
a series 3 and paying a monthly subscription fee (I have a lifetime
subscription). While I think that &lt;a href=&#34;http://www.microsoft.com/windows/products/windowsvista/features/details/mediacenter.mspx&#34;&gt;Media
Center&lt;/a&gt;
is complex, expensive, and unstable for consumers, it&amp;rsquo;s likely HD
support will be cheaper unless TiVo lowers prices, and I&amp;rsquo;m sure things
will get more stable in Media Center world as we move forward.&lt;/p&gt;
&lt;p&gt;Yikes&amp;hellip;long post. Anyway, here&amp;rsquo;s a picture of where I ended up.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2007-06-15/2007-06-15-home-network-Home+Network.jpg&#34;&gt;&lt;img src=&#34;./posts/2007-06-15/thumbnails/2007-06-15-home-network-Home+Network.jpg&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Aaaahhhhh....</title>
      <link>https://emil.lerch.org/2007-06-14-aaaahhhhh/</link>
      <pubDate>Thu, 14 Jun 2007 16:17:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-06-14-aaaahhhhh/</guid>
      <description>&lt;p&gt;Kelly and I have been on a tear lately (as much of a tear as us
analytical/methodical people can be) getting the house the way we want
it. We&amp;rsquo;ve been selling some stuff on craigslist and making a ton of
purchases. Here&amp;rsquo;s a quick list of what I can remember doing over the
last 6 months:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Had house interior painted (this is huge, considering our two-story
formal living room)&lt;/li&gt;
&lt;li&gt;Created a &amp;ldquo;photo wall&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Purchased three bookcases, gave one bookcase to goodwill&lt;/li&gt;
&lt;li&gt;Moved some picture frames to a new wall&lt;/li&gt;
&lt;li&gt;Got an &lt;a href=&#34;http://www.walmart.com/catalog/product.do?product_id=5614670&#34;&gt;entertainment
center&lt;/a&gt;
to replace our old one (known as &amp;ldquo;Pac Man&amp;rdquo; after we stuck our new
&lt;a href=&#34;http://www.google.com/search?q=pioneer+pdp-4214hd&amp;amp;sourceid=navclient-ff&amp;amp;ie=UTF-8&amp;amp;rlz=1B3GGGL_enUS202US202&amp;amp;aq=t&#34;&gt;plasma
TV&lt;/a&gt;
into the space designed for a regular CRT)&lt;/li&gt;
&lt;li&gt;Sold floor standing Infinity speakers, center speaker, and &lt;a href=&#34;http://www.amazon.com/gp/product/B0000X45FI/ref=wl_it_dp/102-6944168-0516131?ie=UTF8&amp;amp;coliid=IN7PQCIKTGLA3&amp;amp;colid=HYDCG4ZLAHWO&#34;&gt;rear
speakers&lt;/a&gt;,
purchasing a full set of &lt;a href=&#34;http://www.amazon.com/Polk-Audio-5-piece-theater-speaker/dp/B000ET2HAS/ref=pd_bbs_sr_1/102-6944168-0516131?ie=UTF8&amp;amp;s=electronics&amp;amp;qid=1181863817&amp;amp;sr=8-1&#34;&gt;Polk
speakers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;(in process) Selling coffee table and end table, with the intension
of purchasing &amp;ldquo;cubes&amp;rdquo; for the family room&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Well, today I got a &lt;a href=&#34;http://www.smarthome.com/59500.html&#34;&gt;hidden IR
system&lt;/a&gt;, which so far is making me
very happy. The problem we ran into after all the entertainment center
business is that the center speaker, which clearly belongs in the
center, would cover the IR sensor on the TV, rendering remotes useless
from one half of the room (the half we use the most). The only way to
alleviate the issue was to move the center speaker all the way to one
edge of the TV or the other, which no longer placed it in the center.
The system has completely alleviated that problem. I&amp;rsquo;ve only used it in
tests so far, but it seems great.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Password safe and folder share</title>
      <link>https://emil.lerch.org/2007-06-13-password-safe-and-folder-share/</link>
      <pubDate>Wed, 13 Jun 2007 10:04:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-06-13-password-safe-and-folder-share/</guid>
      <description>&lt;p&gt;I recently installed both &lt;a href=&#34;http://passwordsafe.sourceforge.net/&#34;&gt;password
safe&lt;/a&gt; and
&lt;a href=&#34;https://www.foldershare.com/&#34;&gt;foldershare&lt;/a&gt;, with the intention of using
them together to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maintain my passwords in a more secure way (password safe)&lt;/li&gt;
&lt;li&gt;Have a copy of the passwords synced on the computers I use
(foldershare)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The solution works great, and is highly recommended. Foldershare is
blocked at work, but my password file doesn&amp;rsquo;t get changed too
frequently, so I&amp;rsquo;m ok with it. Prior to finding foldershare, I actually
started coding my own solution - this was a great find, and I can&amp;rsquo;t wait
to see what Microsoft does with it post-acquisition.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Cookies and affirmative action</title>
      <link>https://emil.lerch.org/2007-06-05-cookies-and-affirmative-action/</link>
      <pubDate>Tue, 05 Jun 2007 05:30:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-06-05-cookies-and-affirmative-action/</guid>
      <description>&lt;p&gt;Yesterday I heard a very strange analogy. There is some political unrest
right now in northern India, and it seems as though the cause of the
unrest boils down to a certain class of citizens that want to be
included into the country&amp;rsquo;s version of affirmative action. The
affirmative action system was established when the country was founded
in order to end the caste system, and was originally intended to be
ended 10 years.&lt;/p&gt;
&lt;p&gt;This discussion happened a few minutes after we were talking about how
Intel acquired a group of people in India who were used to free cookies,
and the near revolt they had on their hands when they tried
(unsuccessfully) to end the perk. They got there eventually, but not
before having to end the lease on the building.&lt;/p&gt;
&lt;p&gt;On explaining the &lt;a href=&#34;http://news.yahoo.com/s/afp/20070601/wl_sthasia_afp/indiaunrestcaste_070601144652&#34;&gt;current
events&lt;/a&gt;,
the comment was made that it&amp;rsquo;s actually pretty similar to the cookie
situation&amp;hellip;once you give people something, they want more, and don&amp;rsquo;t
dare take it away&amp;hellip;&lt;/p&gt;
&lt;p&gt;PS: I couldn&amp;rsquo;t find a good reference point for this URL, but I thought
it might be interesting for my reader(s) to have more background on the
&lt;a href=&#34;http://en.wikipedia.org/wiki/Indian_caste_system&#34;&gt;caste system in
India&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>4AM in India again...</title>
      <link>https://emil.lerch.org/2007-06-04-4am-in-india-again/</link>
      <pubDate>Mon, 04 Jun 2007 16:18:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-06-04-4am-in-india-again/</guid>
      <description>&lt;p&gt;I don&amp;rsquo;t know what it is about my body clock, but very consistently, when
I travel to India from the west coast, I get crazy awake at 4AM for
probably the first week. I arrived in India Sunday around midnight
(that&amp;rsquo;s about noon west coast time). Now it&amp;rsquo;s Tuesday morning at 4AM
(Monday afternoon about 4PM west coast time) and I&amp;rsquo;m wide awake.&lt;/p&gt;
&lt;p&gt;My strategy here usually involves allowing myself some awake
time&amp;hellip;usually about an hour, and then going back to sleep. It may not
be the best strategy, but usually I find that trying to get back to
sleep immediately is pointless anyway, so my real alternatives are to
either indulge myself with some awake time or toss and turn for an hour
trying to get back to sleep.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m only here for a week this time, so my body will probably finish
adjusting just when I&amp;rsquo;m ready to go home.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Coolest tip of the day - for Gmail</title>
      <link>https://emil.lerch.org/2007-05-31-coolest-tip-of-the-day-for-gmail/</link>
      <pubDate>Thu, 31 May 2007 15:20:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-05-31-coolest-tip-of-the-day-for-gmail/</guid>
      <description>&lt;p&gt;I read &lt;a href=&#34;http://www.lifehack.org/articles/technology/20-ways-to-use-gmail-filters.html#more-3328&#34;&gt;this
piece&lt;/a&gt;
on &lt;a href=&#34;http://www.lifehack.org/&#34;&gt;lifehack.org&lt;/a&gt; today, and saw this killer
tip:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Send spam to trash&lt;/strong&gt;. Instead of having Gmail-filtered spam go into
your Spam folder (and have the annoying count of unread spam by the
folder’s name), set up a filter with “is:spam” in the “has the words”
field (just click “OK” on Gmail’s warning dialog box when you click next
step) and “Delete it” as the action. Now all spam messages will go in
your trash.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How to write a question that answers itself</title>
      <link>https://emil.lerch.org/2007-05-31-how-to-write-a-question-that-answers-itself/</link>
      <pubDate>Thu, 31 May 2007 09:36:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-05-31-how-to-write-a-question-that-answers-itself/</guid>
      <description>&lt;p&gt;Just ask a yes/no question in a FAQ about whether or not a feature
exists. The answer to that question is clearly &amp;ldquo;Yes&amp;rdquo;. For instance, this
question from Google calendar&amp;rsquo;s FAQ:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.google.com/support/calendar/bin/answer.py?answer=37228&amp;amp;topic=8567&#34;&gt;Can I check my calendar information via
SMS?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Clearly I can check my calendar information via SMS. I don&amp;rsquo;t see another
question saying &amp;ldquo;Can I check my calendar information via carrier
pigeon?&amp;rdquo;. The reason I don&amp;rsquo;t see this question, is that I can &lt;!-- raw HTML omitted --&gt;not &lt;!-- raw HTML omitted --&gt;check my calendar information that
way, and Google isn&amp;rsquo;t typically going to put a FAQ item out there to
tell people what they won&amp;rsquo;t do&amp;hellip;FAQs are as much of a marketing tool as
a way to disseminate information.&lt;/p&gt;
&lt;p&gt;So the answer is yes, the real question is &lt;!-- raw HTML omitted --&gt;how &lt;!-- raw HTML omitted --&gt;would I do that? Click the link
and find out. Kind of makes you wonder why they don&amp;rsquo;t rephrase the
question as &amp;ldquo;How do I check my calendar information via SMS?&amp;rdquo;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Popular baby names</title>
      <link>https://emil.lerch.org/2007-05-29-popular-baby-names/</link>
      <pubDate>Tue, 29 May 2007 08:28:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-05-29-popular-baby-names/</guid>
      <description>&lt;p&gt;According to&lt;br&gt;
&lt;a href=&#34;http://www.babycenter.com/refcap/1429674.html#0&#34;&gt;popular baby names from around the
world&lt;/a&gt;, my name has
ranked #7 in Finland this year and #10 in Sweden. As my name is
definitely &lt;!-- raw HTML omitted --&gt;not&lt;!-- raw HTML omitted --&gt; known very well
in the US, it&amp;rsquo;s good to see this kind of news from time to time.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Spam</title>
      <link>https://emil.lerch.org/2007-05-29-spam/</link>
      <pubDate>Tue, 29 May 2007 08:00:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-05-29-spam/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve had now 3 different fraud attempts in the last week (we need your
help to claim &lt;!-- raw HTML omitted --&gt;x&lt;!-- raw HTML omitted --&gt; million
dollars&amp;hellip;) appear in my inbox, both at work and at my personal address,
making it past all spam filters. I have another 2 in my junk email at
work, where I hardly get any spam at all. It&amp;rsquo;s odd to me that this has
happened so suddenly. Maybe these people are in the US and have more
time over &lt;a href=&#34;http://www.usmemorialday.org/&#34;&gt;memorial day&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In other spam news, I was going through my spam the other day to make
sure nothing important was being filtered out (I actually found that the
filters were grabbing my &lt;a href=&#34;http://www.vonage.com&#34;&gt;Vonage&lt;/a&gt; email, so it
was a worthwhile exercise). Most of the spam was related to certain
drugs, and it made me wonder what caused these drugs to be responsible
for 80% of my email. This is my conclusion:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Viagra/Cialis/etc. are marketed to help with issues that people
might not want to discuss out in the open - email gives them
anonymity and frees them from embarassment.&lt;/li&gt;
&lt;li&gt;They are expensive, and those that want the drug have a strong
desire to realize some cost savings. This thought is reinforced by
the fact that a good portion of email not relating to these drugs
are referring to ways of getting cheap drugs in general.&lt;/li&gt;
&lt;li&gt;They are expensive, meaning that the spammers realize a lot of
profit if they can sell fraudulent (or maybe even real?) pills.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next in the list was spam related to attempts to gain access to my
personal information
(&lt;a href=&#34;http://en.wikipedia.org/wiki/Phishing&#34;&gt;phishing&lt;/a&gt;), followed by stock
tips. Stock tips have historically been the most successful at getting
through the filters. They tend to also use images containing text,
rather than just sending their message as text in the body. I don&amp;rsquo;t
usually look at any spam that sits in the spam filter, so it&amp;rsquo;s unclear
to me how many other messages use the technique.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Quick tip on google calculator</title>
      <link>https://emil.lerch.org/2007-05-21-quick-tip-on-google-calculator/</link>
      <pubDate>Mon, 21 May 2007 09:36:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-05-21-quick-tip-on-google-calculator/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;./posts/2007-05-21/2007-05-21-quick-tip-on-google-calculator-google+toolbar.png&#34;&gt;&lt;img src=&#34;./posts/2007-05-21/thumbnails/2007-05-21-quick-tip-on-google-calculator-google+toolbar.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;I
noticed a while ago that &lt;a href=&#34;http://toolbar.google.com/&#34;&gt;google toolbar&lt;/a&gt; in
both &lt;a href=&#34;http://www.firefox.com/&#34;&gt;firefox&lt;/a&gt; and
&lt;a href=&#34;http://www.microsoft.com/windows/ie/default.asp&#34;&gt;IE&lt;/a&gt; has a very nice
feature. As you type a search, it will do an autocomplete based on
popular searches. However, if you put in &lt;a href=&#34;http://www.google.com/help/calculator.html&#34;&gt;google calculator
terms&lt;/a&gt;, it will also provide
the calculator answer in the dropdown, so you don&amp;rsquo;t even have to hit
enter to see the answer. I used this yesterday, for instance, to
determine how many teaspoons it takes to make 1/2 a tablespoon (1/2
tblsp in tsp).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>For the love of god, just do it...</title>
      <link>https://emil.lerch.org/2007-05-18-for-the-love-of-god-just-do-it/</link>
      <pubDate>Fri, 18 May 2007 12:04:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-05-18-for-the-love-of-god-just-do-it/</guid>
      <description>&lt;p&gt;Sometimes it&amp;rsquo;s the little things that just drive you nuts. Like all the
meeting requests I get from people with location &amp;ldquo;TBD&amp;rdquo;. At Intel it
takes about 1 minute to schedule a conference room or phone meeting.
You&amp;rsquo;re already spending at least 2 or 3 minutes finding the right
attendees and putting together an agenda for the meeting, why postpone
this minor inconvenience? Inevitably this &amp;ldquo;TBD&amp;rdquo; gets sent in another
update about 15 minutes before the meeting. Spend a little bit of extra
time and save yourself the hassle of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;People who printed out or downloaded meeting information and don&amp;rsquo;t
have network connectivity just before the meeting&lt;/li&gt;
&lt;li&gt;Forgetting about to fill in the TBD completely and having to start
the meeting 5 minutes late as you scramble to schedule something (1
minute because you&amp;rsquo;re late to begin with, 4 minutes trying to go
through a 1 minute procedure while people are IMing you to tell you
that you forgot to add phone conference information)&lt;/li&gt;
&lt;li&gt;Not getting the information to people who have been forwarded the
request&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Getting around lousy software</title>
      <link>https://emil.lerch.org/2007-05-15-getting-around-lousy-software/</link>
      <pubDate>Tue, 15 May 2007 11:21:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-05-15-getting-around-lousy-software/</guid>
      <description>&lt;p&gt;I like HP&amp;rsquo;s printers, but absolutely hate their software. Here&amp;rsquo;s a list
of problems I&amp;rsquo;ve had with them off the top of my head:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Software assumes you can connect to the printer at all times - if
you&amp;rsquo;re not, periodic episodes of 100% CPU utilization occurs.
Disabling WIA (Image Acquisition Service) cured that problem.&lt;/li&gt;
&lt;li&gt;Software periodically crashes for no particular reason, and when not
using the software actively. I believe this is related to the
&amp;ldquo;always connected&amp;rdquo; issue above.&lt;/li&gt;
&lt;li&gt;Software works about 50% of the time when scanning.&lt;/li&gt;
&lt;li&gt;Software is extremely slow to shutdown and sometimes refuses to do
so&lt;/li&gt;
&lt;li&gt;Software blue screens the machine&lt;/li&gt;
&lt;li&gt;Software that likes to update itself but usually can&amp;rsquo;t connect
either due to firewalls (despite proper proxy config) or some other
reason&lt;/li&gt;
&lt;li&gt;Software allows copying data off a memory card inserted into the
printer, but does not copy/set a create date on the files, which
crashes lots of other applications&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I haven&amp;rsquo;t seen the last problem for a while, and a higher % of the
issues I see seem to revolve around scanning functionality. I&amp;rsquo;ve been
trying to document a way to consistently scan without spending an hour
trying to do it, and I&amp;rsquo;ve finally found the way&amp;hellip;avoid the software as
much as possible. Here&amp;rsquo;s my current method:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Insert memory card into printer&lt;/li&gt;
&lt;li&gt;Scan document and send the scan to the memory card&lt;/li&gt;
&lt;li&gt;Copy the files from the memory card to the computer&lt;/li&gt;
&lt;li&gt;Use a &amp;ldquo;touch&amp;rdquo; utility to set the create date on the file to avoid
the last problem above&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As a software guy, this stuff probably bothers me more than most people.
I can&amp;rsquo;t imagine trying to work around all these issues if I were a
normal person - I&amp;rsquo;d probably try to scan once or twice and then return
the product. I understand the need for HP to ship early and ship often,
but you need a complete product&amp;hellip;not 75% of one (hardware and
half-working software).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Not all Wal-Marts are created equal</title>
      <link>https://emil.lerch.org/2007-05-11-not-all-wal-marts-are-created-equal/</link>
      <pubDate>Fri, 11 May 2007 10:46:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-05-11-not-all-wal-marts-are-created-equal/</guid>
      <description>&lt;p&gt;I recently used Wal-Mart&amp;rsquo;s &lt;a href=&#34;http://www.walmart.com/sitetostore&#34;&gt;Site to
Store&lt;/a&gt; (not a bad service!) to buy
an &lt;a href=&#34;http://www.walmart.com/catalog/product.do?product_id=5614670&#34;&gt;entertainment
center&lt;/a&gt;
for our house. The &lt;a href=&#34;http://www.walmart.com/storeLocator/ca_storefinder_details_short.do?rx_title=com.wm.www.apps.storelocator.page.serviceLink.title.default&amp;amp;edit_object_id=2552&amp;amp;rx_dest=%2Findex.gsp&amp;amp;sfsearch_state=&amp;amp;sfsearch_city=&amp;amp;sfsearch_zip=97229&#34;&gt;particular
store&lt;/a&gt;
I had it delivered to, however, was ickier than most Wal-Marts. Allow me
to illustrate this store&amp;rsquo;s placement on a standard statistical
bell-curve of Icky-ness:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2007-05-11/2007-05-11-not-all-wal-marts-are-created-equal-Icky+Wal-Mart.png&#34;&gt;&lt;img src=&#34;./posts/2007-05-11/thumbnails/2007-05-11-not-all-wal-marts-are-created-equal-Icky+Wal-Mart.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Picopad - seems cool so far</title>
      <link>https://emil.lerch.org/2007-05-10-picopad-seems-cool-so-far/</link>
      <pubDate>Thu, 10 May 2007 13:59:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-05-10-picopad-seems-cool-so-far/</guid>
      <description>&lt;p&gt;I just got my
&lt;a href=&#34;http://www.everydayinnovations.com/pages/products_picopad.php&#34;&gt;Picopad&lt;/a&gt;
in the mail and slipped it into my wallet. Everything seems as
advertised so far, but I have yet to try it out in a live environment.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve loved my
&lt;a href=&#34;http://www.victorinox.ch/index.cfm?site=victorinox.ch&amp;amp;page=435&amp;amp;lang=E&#34;&gt;Signature&lt;/a&gt;
knife for a long time, but I&amp;rsquo;ve had a problem with using it while
traveling. I don&amp;rsquo;t like to take it off my keyring and check the knife,
so I&amp;rsquo;m stuck leaving it at home, and it&amp;rsquo;s when I travel that I seem to
need it most. 90% of the time I pull the knife out is to use the pen
that it has. The picopad solves this problem, and gives me something to
write on as well. When not traveling, I&amp;rsquo;ll almost definitely keep my
knife with me, but now it&amp;rsquo;s not so critical.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Very nice trend</title>
      <link>https://emil.lerch.org/2007-05-08-very-nice-trend/</link>
      <pubDate>Tue, 08 May 2007 08:55:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-05-08-very-nice-trend/</guid>
      <description>&lt;p&gt;I like the idea of being able to quickly have a full OS and environment
available for use through the distribution of files for a virtual
machine. Especially with the cost of VM software driving down quickly
(for personal use without support this is now usually free), this is
especially useful for development and testing.&lt;/p&gt;
&lt;p&gt;A couple quick links on this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.rpath.org/rbuilder/project/lamp/release?id=5228&#34;&gt;rBuilder Online - Release: LAMP
appliance&lt;/a&gt;
(I found the latest release to be broken, but this one works - your
results may vary)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.microsoft.com/downloads/details.aspx?familyid=82243606-d16d-445c-8949-9ee8c10cda2e&amp;amp;displaylang=en&#34;&gt;Orcas Community Technology
Preview&lt;/a&gt;.
Next generation Visual Studio on a virtual machine - no need to risk
pre-production software on a machine you care about.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.microsoft.com/downloads/details.aspx?familyid=21EABB90-958F-4B64-B5F1-73D0A413C8EF&amp;amp;displaylang=en&#34;&gt;Windows XP with IE6 or
IE7&lt;/a&gt;.
Great tool for testing&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.vmware.com/vmtn/appliances/directory/community.html&#34;&gt;Directory of user-contributed VMWare images for a variety of
purposes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also there are some conversion tools for those that prefer VPC over
VMWare or vice-versa:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.vmware.com/converter&#34;&gt;VPC to VMWare conversion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://vmtoolkit.com/files/folders/converters/entry8.aspx&#34;&gt;VMWare to VPC
conversion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m sure other converters are available for Xen, etc. As other food for
thought, many virtual hard disks could fit on a USB key, making these
OS&amp;rsquo;s fairly portable.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Dual monitor setup</title>
      <link>https://emil.lerch.org/2007-04-27-dual-monitor-setup/</link>
      <pubDate>Fri, 27 Apr 2007 13:06:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-04-27-dual-monitor-setup/</guid>
      <description>&lt;p&gt;I just changed offices and so I&amp;rsquo;ve taken the opportunity to try out a
new monitor setup. My primary machine is a laptop and I have a external
monitor, so I&amp;rsquo;ve always enjoyed the extra real estate of an extended
desktop, at least when I&amp;rsquo;m in the office. However, I was finding the
setup of limited use the way I had it, with my monitor above the laptop.
After moving to the new office, I put the monitor to the (left) side of
the notebook, and now on day 2 I&amp;rsquo;m finding it to be very comfortable and
useful. Biggest downside so far is that it&amp;rsquo;s on the left side, so I
almost expect it to be the primary monitor (I assume this is my cultural
expectation since I read left to right). I&amp;rsquo;d rather have it on the left
for other reasons, and I don&amp;rsquo;t want it to be considered the primary to
make it easier to dock/undock the laptop without running.&lt;/p&gt;
&lt;p&gt;Just a quick Friday rambling&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Back from Mexico - that&#39;s a big fish!</title>
      <link>https://emil.lerch.org/2007-04-24-back-from-mexico-thats-a-big-fish/</link>
      <pubDate>Tue, 24 Apr 2007 14:57:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-04-24-back-from-mexico-thats-a-big-fish/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;./posts/2007-04-24/2007-04-24-back-from-mexico-thats-a-big-fish-IMG_1700.JPG&#34;&gt;&lt;img src=&#34;./posts/2007-04-24/thumbnails/2007-04-24-back-from-mexico-thats-a-big-fish-IMG_1700.JPG&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here is the head of the fish I caught in Mexico. It&amp;rsquo;s a
&lt;a href=&#34;http://www.anglingdirectholidays.com/pages/rooster_fish.html&#34;&gt;Roosterfish&lt;/a&gt;.
We had a great time hanging out and pretty much lounging, but on Tuesday
we went fishing and I taught the locals a thing or two. This fish is 63&amp;quot;
long and 96lbs. The world record Roosterfish is 66&amp;quot; long and 114lbs. The
fish my brother-in-law caught was no slacker either. We hooked into
these fish within a few seconds of each other, and his fish was 54&amp;quot; long
and 83lbs. In the picture below, we&amp;rsquo;re a little hunched over because
that is really heavy, and slippery.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2007-04-24/2007-04-24-back-from-mexico-thats-a-big-fish-IMG_1708.JPG&#34;&gt;&lt;img src=&#34;./posts/2007-04-24/thumbnails/2007-04-24-back-from-mexico-thats-a-big-fish-IMG_1708.JPG&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Blogger and Outlook 2007</title>
      <link>https://emil.lerch.org/2007-04-12-blogger-and-outlook-2007/</link>
      <pubDate>Thu, 12 Apr 2007 08:17:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-04-12-blogger-and-outlook-2007/</guid>
      <description>&lt;p&gt;I noticed that atom feeds from blogger in Outlook 2007 appear to be
broken. All posts (at least on my machine) show up with a date of
12/31/2006. This &lt;a href=&#34;http://help.blogger.com/bin/answer.py?answer=42662&amp;amp;query=rss&amp;amp;topic=&amp;amp;type=f&#34;&gt;help
file&lt;/a&gt;
shows you how to get to an RSS feed, which works correctly in Outlook.
Note that the trick does &lt;!-- raw HTML omitted --&gt;not&lt;!-- raw HTML omitted --&gt;
work with the old style blog URL (&lt;a href=&#34;http://example.blogspot.com/atom.xml)&#34;&gt;http://example.blogspot.com/atom.xml)&lt;/a&gt;,
but only with the new style URL available only with the new blogger
(&lt;a href=&#34;http://example.blogspot.com/feeds/posts/full?alt=rss)&#34;&gt;http://example.blogspot.com/feeds/posts/full?alt=rss)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://binnyva.blogspot.com/2006/09/blogger-changed-feed-url-for-blogger.html&#34;&gt;This
post&lt;/a&gt;
helped me figure out the new URL. Unfortunately, all I&amp;rsquo;ve seen in
blogger help is &lt;a href=&#34;http://help.blogger.com/bin/answer.py?answer=42663&#34;&gt;how to embed the URL in your
template&lt;/a&gt; rather
than a help file telling you what the URL actually is. For reference,
here are the URLs for this blog, which I&amp;rsquo;ll add to the template
shortly:&lt;/p&gt;
&lt;p&gt;Atom 1.0: &lt;a href=&#34;http://emilsblog.lerch.org/feeds/posts/full&#34;&gt;http://emilsblog.lerch.org/feeds/posts/full&lt;/a&gt;&lt;br&gt;
RSS 2.0: &lt;a href=&#34;http://emilsblog.lerch.org/feeds/posts/full?alt=rss&#34;&gt;http://emilsblog.lerch.org/feeds/posts/full?alt=rss&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Passport saga</title>
      <link>https://emil.lerch.org/2007-04-12-passport-saga/</link>
      <pubDate>Thu, 12 Apr 2007 08:07:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-04-12-passport-saga/</guid>
      <description>&lt;p&gt;We are taking a family vacation to &lt;a href=&#34;http://www.youtube.com/watch?v=L-qo0LBu7Sg&#34;&gt;Casa
Manana&lt;/a&gt; in &lt;a href=&#34;http://www.buceriasmexico.com/&#34;&gt;Bucerias,
Mexico&lt;/a&gt;, just north of &lt;a href=&#34;http://en.wikipedia.org/wiki/Puerto_Vallarta&#34;&gt;Puerto
Vallarta&lt;/a&gt;. We originally
applied for &lt;a href=&#34;http://kathrynmarie.shutterfly.com/&#34;&gt;Kathryn&amp;rsquo;s&lt;/a&gt;
&lt;a href=&#34;http://travel.state.gov/passport/passport_1738.html&#34;&gt;passport&lt;/a&gt; back on
January 18th, and were given March 15th as the date for the passport to
get here in the mail. Due to the &lt;a href=&#34;http://www.state.gov/r/pa/prs/ps/2006/76752.htm&#34;&gt;recent changes in travel
regulations&lt;/a&gt;, we
expected it would take longer, but we decided 3 months (12 weeks) was
plenty of time, and we did not need &lt;a href=&#34;http://travel.state.gov/passport/get/processing/processing_1740.html&#34;&gt;expedited
service&lt;/a&gt;.
The web site currently claims processing times of 10 weeks, although I
believe at the time it was saying 8 weeks.&lt;/p&gt;
&lt;p&gt;March 15th flew by, so we checked the &lt;a href=&#34;http://travel.state.gov/passport/get/status/status_2567.html&#34;&gt;status
online&lt;/a&gt;
around March 29th. The site said that we&amp;rsquo;d get the passport April 9th.
This was pretty close to our April 15th departure, so I called on April
3rd to find out if this was a problem, and if so, what I could do about
it. When I called, I literally got &amp;ldquo;all circuits are busy, please try
again later&amp;rdquo;. A redial later, I got through, and the phone menu starts
with, &amp;ldquo;We are currently experiencing unprecedented demand for US
Passports. We have added staff and are working overtime to make sure
that no travel plans are effected. If you are not traveling in the next
two weeks, PLEASE, hang up. (then something to the effect of &amp;ldquo;it will be
ok&amp;rdquo;)&amp;rdquo;. Option 1 and option 3 later, I&amp;rsquo;m told, &amp;ldquo;all our agents are busy,
we can not answer your call at this time&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Several more redials later, and I actually get into the queue. I wait a
relatively short 30 minutes and the agent tells me, &amp;ldquo;oh, you&amp;rsquo;re in good
shape, this is in book print&amp;rdquo;. I asked what that meant, and they said
that it stays in book print for a day and then someone physically
compares the passport to the application, a process that also takes a
day. Then it gets shipped out priority mail (2-3 days). The woman told
me that with Easter and everything, and &amp;ldquo;you know how the government is&amp;rdquo;
(I love that coming from a government employee), that I should see it
Saturday or Monday.&lt;/p&gt;
&lt;p&gt;Monday goes by, and no passport. Tuesday goes by, and no passport. So I
check the status online after the mail came, and now it says that it
shipped, but it&amp;rsquo;s estimated to arrive &amp;ldquo;on or about 4/14/07&amp;rdquo;. Umm&amp;hellip;we
&lt;!-- raw HTML omitted --&gt;leave&lt;!-- raw HTML omitted --&gt; on 4/15, so what do they
mean with that &amp;ldquo;about&amp;rdquo;? So, I give them another call on Tuesday (April
10th&amp;hellip;5 days before we fly). After the prerequisite redialing and 30
minute wait, I get through and they said I should be in good shape. It
physically was on a truck on April 9th, so I should see it Thursday or
Friday (April 12th or 13th).&lt;/p&gt;
&lt;p&gt;The story does have a good ending, as the passport has arrived today
(April 11th). Talk about cutting it close, though&amp;hellip;&lt;/p&gt;
&lt;p&gt;As a quick postscript to a long post, Kathryn got one of the new &lt;a href=&#34;http://travel.state.gov/passport/eppt/eppt_2498.html&#34;&gt;RFID
passports&lt;/a&gt;, and
it&amp;rsquo;s really fancy. I think they did a very good job with the &lt;a href=&#34;http://travel.state.gov/passport/eppt/epptnew_2807.html&#34;&gt;design and
artwork&lt;/a&gt;, and I
can only assume it&amp;rsquo;s less subject to counterfeiting. I&amp;rsquo;m not sure how
well the &lt;a href=&#34;http://travel.state.gov/passport/eppt/eppt_2788.html&#34;&gt;face
recognition&lt;/a&gt; will
work with Kathryn when she&amp;rsquo;s 4 years old, though.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Calendaring events way in the future</title>
      <link>https://emil.lerch.org/2007-04-11-calendaring-events-way-in-the-future/</link>
      <pubDate>Wed, 11 Apr 2007 16:07:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-04-11-calendaring-events-way-in-the-future/</guid>
      <description>&lt;p&gt;So after receiving Kathryn&amp;rsquo;s passport, I had to deal with the fact that
it will expire, and that I have to remember to renew the passport when
it does. I believe that software definitely has a half-life, so I had to
question the use of &lt;a href=&#34;http://calendar.google.com&#34;&gt;Google calendar&lt;/a&gt;, which
for our family is at &lt;a href=&#34;http://calendar.lerch.org&#34;&gt;calendar.lerch.org&lt;/a&gt;
through the Google &lt;a href=&#34;http://www.google.com/a/&#34;&gt;apps for your domain&lt;/a&gt;
service.&lt;/p&gt;
&lt;p&gt;While I like the service, will it still be what I&amp;rsquo;m using when my
passport expires in 2014? What&amp;rsquo;s the alternative reminder system (on or
offline) that I can expect to be using at that time? In the end I
decided that if not using Google calendar I would be migrating the data
to whatever would be in use at that time. Since reminders work via SMS,
I&amp;rsquo;m also assuming that the reminder system migrates to whatever is in
use.&lt;/p&gt;
&lt;p&gt;I guess the fall back is to just check the passports before travel, but
the general issue still exists. Given the transient nature of systems
(software or process), what assumptions can you make to ensure that you
get when you expect in 1 year, 5 years, or 10 years?&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Mappy Hour</title>
      <link>https://emil.lerch.org/2007-04-11-mappy-hour/</link>
      <pubDate>Wed, 11 Apr 2007 09:00:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-04-11-mappy-hour/</guid>
      <description>&lt;p&gt;I was just forwarded a link to the &lt;a href=&#34;http://mappyhour.nerl.net/index.php?ulng=en&#34;&gt;cool site of the
day&lt;/a&gt;. It&amp;rsquo;s a mashup
combining happy hour information with Google maps. Awesome.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Blog URL change</title>
      <link>https://emil.lerch.org/2007-04-06-blog-url-change/</link>
      <pubDate>Fri, 06 Apr 2007 08:59:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-04-06-blog-url-change/</guid>
      <description>&lt;p&gt;The current URL will still work, but I&amp;rsquo;ve set the blog up so it can now
be accessed via emilsblog.lerch.org.&lt;/p&gt;
&lt;p&gt;Our family blog is at blog.lerch.org.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Coolest thing I&#39;ve seen all day</title>
      <link>https://emil.lerch.org/2007-04-05-coolest-thing-ive-seen-all-day/</link>
      <pubDate>Thu, 05 Apr 2007 16:28:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-04-05-coolest-thing-ive-seen-all-day/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;http://maps.google.com&#34;&gt;Google maps&lt;/a&gt; launched a way to annotate and
save your own custom maps. I&amp;rsquo;ve started a map of my &lt;a href=&#34;http://maps.google.com/maps/ms?ie=UTF8&amp;amp;hl=en&amp;amp;msa=0&amp;amp;msid=115347831055055936027.00000111c2fa87f6a4242&#34;&gt;overall
travels&lt;/a&gt;,
and one specifically focused on places I&amp;rsquo;ve been in India. I see this
being a big project, but it would be nice to have URLs to pictures, etc.
at each site.&lt;/p&gt;
&lt;p&gt;More tangible value for this can be seen in the map I&amp;rsquo;ve done to show
&lt;a href=&#34;http://maps.google.com/maps/ms?ie=UTF8&amp;amp;hl=en&amp;amp;msa=0&amp;amp;msid=115347831055055936027.00000111c286e0ab27aee&#34;&gt;where to park and how to get
to&lt;/a&gt;
&lt;a href=&#34;http://www.bethanycommunity.net&#34;&gt;Bethany Community Network&lt;/a&gt; meetings.&lt;/p&gt;
&lt;p&gt;Cool.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>SubSonic - The Zero Code DAL</title>
      <link>https://emil.lerch.org/2007-03-23-subsonic-the-zero-code-dal/</link>
      <pubDate>Fri, 23 Mar 2007 16:18:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-03-23-subsonic-the-zero-code-dal/</guid>
      <description>&lt;p&gt;If you&amp;rsquo;re a .NET person with ruby on rails envy, check out &lt;a href=&#34;http://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=actionpack&#34;&gt;SubSonic -
The Zero Code
DAL&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Holy crap that&#39;s a good router</title>
      <link>https://emil.lerch.org/2007-03-21-holy-crap-thats-a-good-router/</link>
      <pubDate>Wed, 21 Mar 2007 10:24:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-03-21-holy-crap-thats-a-good-router/</guid>
      <description>&lt;p&gt;So my &lt;a href=&#34;http://emillerch.blogspot.com/2006/09/21-hours-and-counting.html&#34;&gt;router
problems&lt;/a&gt;
came back mysteriously, and disappeared just as mysteriously several
days later. I&amp;rsquo;m still not sure what the problem was, but in the meantime
I decided I had spent enough of my time trying to diagnose the problem
and bought a &lt;a href=&#34;http://www.dlink.com/products/?pid=530&#34;&gt;DLink DIR-655&lt;/a&gt;.
The router still requires a lot of reboots during configuration, but not
nearly as much as my original one. However, this is rock solid, and
fast. I couldn&amp;rsquo;t believe my eyes while I&amp;rsquo;m on the phone (using VoIP
through the Internet) and watching a 435MB download (&lt;a href=&#34;http://msdn.microsoft.com/vstudio/support/vs2005sp1/default.aspx&#34;&gt;Visual Studio
SP1&lt;/a&gt;)
clicking off about 1MB/sec, which is nearly my rated download speed
through Verizon FIOS.&lt;/p&gt;
&lt;p&gt;Oh, and I was also connected to work at the time. Also, I haven&amp;rsquo;t had a
single unscheduled reboot since setting it up.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Membership and Role providers for MySQL</title>
      <link>https://emil.lerch.org/2007-03-21-membership-and-role-providers-for-mysql/</link>
      <pubDate>Wed, 21 Mar 2007 10:15:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-03-21-membership-and-role-providers-for-mysql/</guid>
      <description>&lt;p&gt;Stumbled on this the other day while looking for a way to use MySQL with
ASP.NET and thought I&amp;rsquo;d share.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.codeproject.com/aspnet/MySQLMembershipProvider.asp&#34;&gt;Membership and Role providers for MySQL - The Code Project -
ASP.NET&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Is all press good press?</title>
      <link>https://emil.lerch.org/2007-03-19-is-all-press-good-press/</link>
      <pubDate>Mon, 19 Mar 2007 12:37:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-03-19-is-all-press-good-press/</guid>
      <description>&lt;p&gt;I just noticed &lt;a href=&#34;http://www.windows-now.com/blogs/robert/archive/2007/03/07/internal-amd-memo-encourages-rapid-company-wide-vista-adoption.aspx&#34;&gt;this
post&lt;/a&gt;,
and it&amp;rsquo;s interesting to me that a company&amp;rsquo;s internal OS upgrade schedule
would be taken so seriously, but this is Intel, and that, I guess, makes
things a little different.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>New Role</title>
      <link>https://emil.lerch.org/2007-03-16-new-role/</link>
      <pubDate>Fri, 16 Mar 2007 10:42:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-03-16-new-role/</guid>
      <description>&lt;p&gt;So, I&amp;rsquo;m all set&amp;hellip;getting settled into a new role as a capability
manager at Intel, working on non-Direct customer applications and data.
If that&amp;rsquo;s clear to you, let me know because I have some questions
myself. ;-)&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s been quite the start - this is actually a very high priority in our
group right now due to the variety of systems that contain unique sets
of customer data. Everything is coming to a head right now due to a
desire to converge onto a single platform, so my first program and
project will be on the critical path to at least 5 other programs. I&amp;rsquo;ve
been in 3 days of meetings in my new role already this week (while
trying to carve time out for transition out of my old role), and I only
knew what I&amp;rsquo;d be doing Monday afternoon. Yuck. Anyway, enough work
stuff&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Cool site of the day:  http://becomeanmm.com</title>
      <link>https://emil.lerch.org/2007-03-14-cool-site-of-the-day-http-becomeanmm-com/</link>
      <pubDate>Wed, 14 Mar 2007 14:58:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2007-03-14-cool-site-of-the-day-http-becomeanmm-com/</guid>
      <description>&lt;p&gt;ok - this is cool. Thanks
&lt;a href=&#34;http://mattdinovo.spaces.live.com/Blog/cns%21C4208C6FFC36195F%21214.entry&#34;&gt;Matt&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a picture of me from the site:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2007-03-14/2007-03-14-cool-site-of-the-day-http-becomeanmm-com-EmilAsMandM.png&#34;&gt;&lt;img src=&#34;./posts/2007-03-14/thumbnails/2007-03-14-cool-site-of-the-day-http-becomeanmm-com-EmilAsMandM.png&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Role changes</title>
      <link>https://emil.lerch.org/2007-03-09-role-changes/</link>
      <pubDate>Fri, 09 Mar 2007 14:31:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2007-03-09-role-changes/</guid>
      <description>&lt;p&gt;It appears as though my time in my current position (as a product
manager for systems supporting our customer support processes) at Intel
is about to end. The new adventure is still to be determined, but I&amp;rsquo;m
rapidly closing in on 3 alternatives. I should have more information,
but not necessarily a final answer, sometime next week.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>It depends</title>
      <link>https://emil.lerch.org/2007-03-09-it-depends/</link>
      <pubDate>Fri, 09 Mar 2007 14:30:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2007-03-09-it-depends/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve heard the expression that the answer is almost always, &amp;ldquo;it
depends&amp;rdquo;. Whether or not the answer is &amp;ldquo;it depends&amp;rdquo;, of course, depends
on certain factors. ;-) Math problems usually have a concrete answer,
for instance, at least until you get into more advanced subjects.&lt;/p&gt;
&lt;p&gt;One thing I try to keep in mind, however, is that an expert in the field
can always tell you what the answer to your question depends on. A
novice will generally give you an answer without first understanding the
background and context for your question, thereby ignoring critical
factors that your answer depends upon, or simply tell you that the
answer depends, but will not be able to articulate what it is that will
help you come to a conclusion.&lt;/p&gt;
&lt;p&gt;To separate experts from posers, ask a question and see if the person
can tell you the factors that would influence the answer.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Virtualization bad for hardware sales?</title>
      <link>https://emil.lerch.org/2007-03-08-virtualization-bad-for-hardware-sales/</link>
      <pubDate>Thu, 08 Mar 2007 14:56:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2007-03-08-virtualization-bad-for-hardware-sales/</guid>
      <description>&lt;p&gt;I just read &lt;a href=&#34;http://blogs.msdn.com/virtual_pc_guy/archive/2007/01/22/is-virtualization-bad-for-hardware-sales.aspx&#34;&gt;this
blog&lt;/a&gt;,
which points to &lt;a href=&#34;http://blogs.zdnet.com/BTL/?p=4324&#34;&gt;this article&lt;/a&gt;, and
I disagree. Virtualization is good for hardware sales, because it
shortens the hardware refresh cycle. It will certainly cause a shift
toward a fewer number of larger, more expensive servers, but it also
means that replacing those servers with faster ones will be a lot
simpler, and companies will be much more likely to throw more hardware
at performance problems than they were in the past.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>What good is a phone call if you can&#39;t speak?</title>
      <link>https://emil.lerch.org/2007-02-14-what-good-is-a-phone-call-if-you-cant-speak/</link>
      <pubDate>Wed, 14 Feb 2007 14:34:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2007-02-14-what-good-is-a-phone-call-if-you-cant-speak/</guid>
      <description>&lt;p&gt;So having a daughter is fantastic, but it&amp;rsquo;s been interesting to see how
logistics work when you can&amp;rsquo;t sign your name&amp;hellip;here are two recent
logistics issues and how they needed to be handled:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Obtaining a passport:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Kathryn had to get a picture taken, and show up with both Kelly and
I at the passport office with photos in hand, a birth certificate
showing Kathryn was born in the US and naming Kelly and I as
parents, and our drivers licenses to prove we actually are the
parents&lt;/li&gt;
&lt;li&gt;Once we get her passport, it is not valid until signed. The
signature? One of us prints Kathryn&amp;rsquo;s name and then signs our own
name, adding (MOTHER) or (FATHER) after the signature. Here&amp;rsquo;s the
&lt;a href=&#34;http://travel.state.gov/passport/fri/faq/faq_1741.html#ca63&#34;&gt;official FAQ
item&lt;/a&gt;.
Oh, and &lt;a href=&#34;http://travel.state.gov/passport/get/minors/minors_834.html&#34;&gt;the
requirements&lt;/a&gt;
for obtaining a passport for minors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2. Depositing a check (that was addressed payable to child):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Account needs to be opened as UTMA (or UGMA if you&amp;rsquo;re from a few
states that don&amp;rsquo;t recognize UTMA yet). Parent is custodian.&lt;/li&gt;
&lt;li&gt;Check needs to be signed by me as a custodian, payable to the bank,
for benefit of the child. For Kathryn, the full check endorsement
went something like this: Custodian for Kathryn payable to / FBO
Kathryn&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also fun is the fact that certain things require an email address.
Kathryn&amp;rsquo;s email address is established now, but it will be several years
before she reads her own email&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Light reading</title>
      <link>https://emil.lerch.org/2007-02-13-light-reading/</link>
      <pubDate>Tue, 13 Feb 2007 11:30:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2007-02-13-light-reading/</guid>
      <description>&lt;p&gt;On the way back from India I read an article (PDF) on &lt;a href=&#34;https://institutional5.vanguard.com/iip/pdf/CRR_Roth_401k.pdf&#34;&gt;tax
diversification&lt;/a&gt;
when working on retirement savings planning. I thought it was an
exceptional article&amp;hellip;absolutely recommended.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Driving in India</title>
      <link>https://emil.lerch.org/2007-02-05-driving-in-india/</link>
      <pubDate>Mon, 05 Feb 2007 21:43:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2007-02-05-driving-in-india/</guid>
      <description>&lt;p&gt;Unless you&amp;rsquo;re native, don&amp;rsquo;t do it. I just uploaded a few short videos of
the driving experience this past weekend in
&lt;a href=&#34;http://www.youtube.com/watch?v=Sgow_rkDpw0&#34;&gt;Delhi&lt;/a&gt; and in
&lt;a href=&#34;http://www.youtube.com/watch?v=1C8oKtyNpVs&#34;&gt;Agra&lt;/a&gt;. I&amp;rsquo;ve been staying in
Bangalore, but the driving experience is pretty much the same. I also
have a very short (but boring - buildings don&amp;rsquo;t do a lot of stuff) video
of visiting the &lt;a href=&#34;http://www.youtube.com/watch?v=D6DJ6lm2x5Y&#34;&gt;Taj Mahal&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Trip to the Taj Mahal/Fatehpur Sikri</title>
      <link>https://emil.lerch.org/2007-02-05-trip-to-the-taj-mahal-fatehpur-sikri/</link>
      <pubDate>Mon, 05 Feb 2007 02:41:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2007-02-05-trip-to-the-taj-mahal-fatehpur-sikri/</guid>
      <description>&lt;p&gt;So I just finished uploading pictures of my trip to the &lt;a href=&#34;http://thelerchs.shutterfly.com/action/pictures?a=67b0de21b3254cae841f&#34;&gt;Taj
Mahal&lt;/a&gt;
and &lt;a href=&#34;http://thelerchs.shutterfly.com/action/pictures?a=67b0de21b3254ca10520&#34;&gt;Fatehpur
Sikri&lt;/a&gt;.
Fatehpur Sikri was very impressive, partly because I had no idea what to
expect when I got there (by contrast, most people have an idea of what
the Taj looks like and what it&amp;rsquo;s about).&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;http://en.wikipedia.org/wiki/Fatehpur_Sikri&#34;&gt;wikipedia entry&lt;/a&gt; gives
some background on the site, but doesn&amp;rsquo;t mention a very important fact:
I&amp;rsquo;m told that Akbar had 3 wives, one Christian, one Hindu, and one
Muslim. The entire architecture of the place incorporates themes from
all three religions. An example - the gate called &lt;a href=&#34;http://en.wikipedia.org/wiki/Buland_Darwaza&#34;&gt;Buland
Darwaza&lt;/a&gt; is the same height
as the Obelisk in &lt;a href=&#34;http://en.wikipedia.org/wiki/St._Peter%27s_Basilica&#34;&gt;St. Peter&amp;rsquo;s
Basilica&lt;/a&gt; as an
homage to Christians. The wikipedia entries even confirm this. Some of
the &lt;a href=&#34;http://thelerchs.shutterfly.com/action/pictures?a=67b0de21b3254ca10520&#34;&gt;pictures I
took&lt;/a&gt;
show the various styles. Here&amp;rsquo;s &lt;a href=&#34;http://thelerchs.shutterfly.com/action/slideshow?a=67b0de21b3254ca10520&amp;amp;auto=0&amp;amp;idx=33&amp;amp;m=1&amp;amp;d=1170676108586&#34;&gt;another
example&lt;/a&gt;
of what I&amp;rsquo;m talking about.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also working on getting a translation for &lt;a href=&#34;http://thelerchs.shutterfly.com/action/slideshow?a=67b0de21b3254ca10520&amp;amp;auto=0&amp;amp;idx=75&amp;amp;m=1&amp;amp;d=1170676401386&#34;&gt;this Arabic
plaque&lt;/a&gt;.
It&amp;rsquo;s artistic in nature so it&amp;rsquo;s not easy to read, even for natives (I
have a coworker who is Egyptian).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Yikes - long time</title>
      <link>https://emil.lerch.org/2007-02-05-yikes-long-time/</link>
      <pubDate>Mon, 05 Feb 2007 02:39:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2007-02-05-yikes-long-time/</guid>
      <description>&lt;p&gt;So I&amp;rsquo;ve totally gone dark. Short summary of what I&amp;rsquo;ve been up to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Crazy work stuff (December)&lt;/li&gt;
&lt;li&gt;Christmas with wife/daughter in Chicago&lt;/li&gt;
&lt;li&gt;Crazy work stuff (January)&lt;/li&gt;
&lt;li&gt;Business trip to India&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m India right now. Will try to post more&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>It&#39;s not surprising at all that people search for Yahoo</title>
      <link>https://emil.lerch.org/2006-11-30-its-not-surprising-at-all-that-people-search-for-yahoo/</link>
      <pubDate>Thu, 30 Nov 2006 11:06:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-11-30-its-not-surprising-at-all-that-people-search-for-yahoo/</guid>
      <description>&lt;p&gt;I couldn&amp;rsquo;t agree more. Very similar concepts to the &lt;a href=&#34;http://blogs.msdn.com/brada/archive/2003/10/02/50420.aspx&#34;&gt;pit of
success&lt;/a&gt;,
which I have posted outside my cube.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://blogs.msdn.com/oldnewthing/archive/2006/11/27/1160055.aspx&#34;&gt;The Old New Thing : It&amp;rsquo;s not surprising at all that people search for
Yahoo&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Interesting conclusion...where did that come from?</title>
      <link>https://emil.lerch.org/2006-11-15-interesting-conclusion-where-did-that-come-from/</link>
      <pubDate>Wed, 15 Nov 2006 11:08:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-11-15-interesting-conclusion-where-did-that-come-from/</guid>
      <description>&lt;p&gt;I was surprised to see a stock I own
(&lt;a href=&#34;http://finance.yahoo.com/q?s=INPC&#34;&gt;INPC&lt;/a&gt;) on this list today:&lt;br&gt;
&lt;a href=&#34;http://biz.yahoo.com/seekingalpha/061113/20405_id.html?.v=1&#34;&gt;Russell 3000&amp;rsquo;s Best and Worst Performing Stocks: Financial News -
Yahoo!
Finance&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It says YTD performance for INPC is 279%. I&amp;rsquo;m not sure how they get
that&amp;hellip;the closest that I find is a 140% gain if you bought at the 52
week low (5.02) and sold at the 52 week high (12.07). I don&amp;rsquo;t want to
call them a liar or anything, but if YTD is defined as Jan 1 (8.76) to
today(11.38), that is a 30% gain. If you bought when I did, you&amp;rsquo;re still
down about 20% but a lot happier than when the stock was at $5/share.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>IE7 Failed Experiment</title>
      <link>https://emil.lerch.org/2006-11-13-ie7-failed-experiment/</link>
      <pubDate>Mon, 13 Nov 2006 09:58:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-11-13-ie7-failed-experiment/</guid>
      <description>&lt;p&gt;With the recent production release of &lt;a href=&#34;http://www.microsoft.com/windows/ie/default.mspx&#34;&gt;Internet Explorer
7&lt;/a&gt;, I thought it was
time to revisit my &lt;a href=&#34;http://emillerch.blogspot.com/2006/09/thats-bold-movewonder-how-thatll-work.html&#34;&gt;default browser
decision&lt;/a&gt;.
I&amp;rsquo;ve been fairly happy with &lt;a href=&#34;http://www.getfirefox.com&#34;&gt;Firefox&lt;/a&gt;, but
have had a few annoyances:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copy/paste of HTML tables into applications (Microsoft Word and
Excel) doesn&amp;rsquo;t work nearly as well as with IE&lt;/li&gt;
&lt;li&gt;Proxy changes don&amp;rsquo;t happen without at least an extra trip to the
options. To be fair, IE 6 didn&amp;rsquo;t handle it at all, but since the
browser lacked tabs, starting a new instance wasn&amp;rsquo;t a difficulty&lt;/li&gt;
&lt;li&gt;Some internal web sites designed the authentication in a manner that
required another click to get in&lt;/li&gt;
&lt;li&gt;When printing, some pages arrive with parts chopped off from the
right&lt;/li&gt;
&lt;li&gt;Some web sites (e.g. Wachovia) would break randomly for no apparent
reason. Sometimes they would work, sometimes not&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Well, I installed IE7 and it seemed ok. I found a &lt;a href=&#34;http://www.ysgyfarnog.co.uk/utilities/mousegestures/&#34;&gt;mouse gesture
plugin&lt;/a&gt; to
compensate for the lack of something built in. I tried setting it as my
default browser, but after using it a bit I had it crash a couple times.
The session manager built in apparently assumes this will never happen,
because not once did it restore my session from a crash, which is
something that I&amp;rsquo;ve had for over a year in Firefox via the &lt;a href=&#34;http://tmp.garyr.net/&#34;&gt;Tab Mix
Plus&lt;/a&gt; extension.&lt;/p&gt;
&lt;p&gt;Other than the fatal flaw, I agree wholeheartedly with the &lt;a href=&#34;http://reviews.cnet.com/Internet_Explorer_7/4505-3514_7-32111537.html#more&#34;&gt;Cnet.com
review&lt;/a&gt;
of IE7. Firefox remains my default browser.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Not sure why I need this, but it&#39;s freakin&#39; cool</title>
      <link>https://emil.lerch.org/2006-11-10-not-sure-why-i-need-this-but-its-freakin-cool/</link>
      <pubDate>Fri, 10 Nov 2006 15:35:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-11-10-not-sure-why-i-need-this-but-its-freakin-cool/</guid>
      <description>&lt;p&gt;So I went to my usual yearly eye exam the other day, and they had a new
piece of equipment to help make sure that my retinas were healthy. It
takes a picture of your retinas. Anyway, after the deal was done, the
technician asked me if I wanted the photos emailed to me. Do I need
that? Absolutely not. Is it cool? Yes. My response? Totally email it!&lt;/p&gt;
&lt;p&gt;Without further ado&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2006-11-10/2006-11-10-not-sure-why-i-need-this-but-its-freakin-cool-Left-20061108%2540111517.jpg&#34;&gt;&lt;img src=&#34;./posts/2006-11-10/thumbnails/2006-11-10-not-sure-why-i-need-this-but-its-freakin-cool-Left-20061108%2540111517.jpg&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;./posts/2006-11-10/2006-11-10-not-sure-why-i-need-this-but-its-freakin-cool-Right-20061108%2540111450.jpg&#34;&gt;&lt;img src=&#34;./posts/2006-11-10/thumbnails/2006-11-10-not-sure-why-i-need-this-but-its-freakin-cool-Right-20061108%2540111450.jpg&#34; alt=&#34;&#34;&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Election results</title>
      <link>https://emil.lerch.org/2006-11-08-election-results/</link>
      <pubDate>Wed, 08 Nov 2006 09:02:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-11-08-election-results/</guid>
      <description>&lt;p&gt;Interesting day. Looks like the results are in, and democrats are taking
control of at least the house. Call me cynical, but having different
parties in control of different branches is my best case scenario.
Gridlock is great&amp;hellip;do we really want government messing things up
more?&lt;/p&gt;
&lt;p&gt;In Oregon, it looks like everything is going to the democrats, so I
don&amp;rsquo;t get my gridlock. I guess you win some and lose some.&lt;/p&gt;
&lt;p&gt;My favorite story of the day: Oregonians didn&amp;rsquo;t understand the
assignment. Measures 46 and 47 in Oregon were dedicated to campaign
finance reform, and were dependent on each other. Either they both
needed to pass or nothing would happen. Well, measure 46 failed while
measure 47 passed. The results weren&amp;rsquo;t even that close on either race.&lt;/p&gt;
&lt;p&gt;Lastly, it looks like we&amp;rsquo;ll pass a $195M bond measure for local schools.
Kathryn voiced her support of that!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>SQL Server Hosting Toolkit</title>
      <link>https://emil.lerch.org/2006-11-07-sql-server-hosting-toolkit/</link>
      <pubDate>Tue, 07 Nov 2006 09:23:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-11-07-sql-server-hosting-toolkit/</guid>
      <description>&lt;p&gt;Just stumbled across this in my travels, and it looks pretty cool. This
&lt;a href=&#34;http://www.codeplex.com/Wiki/View.aspx?ProjectName=sqlhost&amp;amp;title=Database%20Publishing%20Wizard&#34;&gt;SQL Server Hosting
Toolkit&lt;/a&gt;
will generate database scripts from one database to deploy to another
database, in an environment where full access is not available, say at
&lt;a href=&#34;http://www.godaddy.com&#34;&gt;GoDaddy&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Faking the soda machine</title>
      <link>https://emil.lerch.org/2006-11-06-faking-the-soda-machine/</link>
      <pubDate>Mon, 06 Nov 2006 14:39:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-11-06-faking-the-soda-machine/</guid>
      <description>&lt;p&gt;Quick tip to keep the amount of change in your pocket to a minimum.
Suppose you only have a large bill in your pocket, and you want a soda
(which of course is the only appropriate term for the substance). The
soda machine will always stop accepting money after you&amp;rsquo;ve met or
exceeded the amount of the highest price item.&lt;/p&gt;
&lt;p&gt;The solution? Put in the change followed by your large bill. With any
luck, you&amp;rsquo;ll get dollar coins rather than a mess of smaller change. For
example, I paied for a $1.35 soda the other day with 35 cents followed
by a $5 bill, resulting in 4 dollar coins rather than 3 dollar coins, 2
quarters, a dime and a nickel.&lt;/p&gt;
&lt;p&gt;I, of course, think the typical soda machine user interface to be
fatally flawed due to the inability of it to take more money than the
highest price item (although I understand that the manufacturers don&amp;rsquo;t
want the machines to become change machines). That debate we can have
another time&amp;hellip;for now, enjoy the workaround.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Small decisions that signal big ones</title>
      <link>https://emil.lerch.org/2006-11-02-small-decisions-that-signal-big-ones/</link>
      <pubDate>Thu, 02 Nov 2006 15:20:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-11-02-small-decisions-that-signal-big-ones/</guid>
      <description>&lt;p&gt;The other day I was visited by someone with a stack of manuals for a
technology/product that we&amp;rsquo;re using in our group. I was asked whether we
should throw those manuals out of keep them. I told them to go ahead and
throw it out.&lt;/p&gt;
&lt;p&gt;This is a technology/product that is currently in use. We&amp;rsquo;re moving to a
new technology/product, and that small decision to throw out the manuals
felt to me more like a massive strategic decision that yes, we&amp;rsquo;re
committed to the new direction.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Bush keeps Rumsfeld, Cheney</title>
      <link>https://emil.lerch.org/2006-11-01-bush-keeps-rumsfeld-cheney/</link>
      <pubDate>Wed, 01 Nov 2006 13:50:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-11-01-bush-keeps-rumsfeld-cheney/</guid>
      <description>&lt;p&gt;Here&amp;rsquo;s something interesting I heard yesterday. Bush came out and
announced he will &lt;a href=&#34;http://abcnews.go.com/Politics/wireStory?id=2621650&#34;&gt;hold onto Rusmfeld and Cheney until the end of his
administration&lt;/a&gt;.
Despite what you may think of the administration, it says a lot that
Bush has to come out and make that kind of statement.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Sometimes you need to think through the ramifications</title>
      <link>https://emil.lerch.org/2006-11-01-sometimes-you-need-to-think-through-the-ramifications/</link>
      <pubDate>Wed, 01 Nov 2006 10:28:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-11-01-sometimes-you-need-to-think-through-the-ramifications/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m currently on hold with my
&lt;a href=&#34;http://dch.ohsuhealth.com/index.cfm?pageid=502%C2%A7ionID=117&amp;amp;open=120&amp;amp;cfid=6&amp;amp;cftoken=59572841&#34;&gt;pediatrician&lt;/a&gt;
and was amused by the phone announcement. After the usual &amp;ldquo;if this is an
emergency please hang up and call 911&amp;rdquo; yada yada, it mentioned that &amp;ldquo;if
you are a non-english speaker, please call us at xxx-xxx-xxxx&amp;rdquo;. Well, if
I were a non-english speaker, would I really understand that request?
The note was never repeated in Spanish (or any other language).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>My Firefox tweaks</title>
      <link>https://emil.lerch.org/2006-10-30-my-firefox-tweaks/</link>
      <pubDate>Mon, 30 Oct 2006 19:54:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-30-my-firefox-tweaks/</guid>
      <description>&lt;p&gt;For speed, I&amp;rsquo;ve enabled
&lt;a href=&#34;http://www.mozilla.org/projects/netlib/http/pipelining-faq.html&#34;&gt;pipelining&lt;/a&gt;.
Here&amp;rsquo;s how:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;about:config in the address bar&lt;/li&gt;
&lt;li&gt;type &amp;ldquo;pipelining&amp;rdquo; in the filter&lt;/li&gt;
&lt;li&gt;set &amp;ldquo;network.http.pipelining&amp;rdquo; to true&lt;/li&gt;
&lt;li&gt;set &amp;ldquo;network.http.pipelining.maxrequests&amp;rdquo; to 8&lt;/li&gt;
&lt;li&gt;set &amp;ldquo;network.http.proxy.pipelining&amp;rdquo; to true&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For access at Intel, I also turned on http authentication:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;set &amp;ldquo;network.automatic-ntlm-auth.trusted-uris&amp;rdquo; to &amp;ldquo;intel.com&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Optimizing Page Load Time</title>
      <link>https://emil.lerch.org/2006-10-30-optimizing-page-load-time/</link>
      <pubDate>Mon, 30 Oct 2006 19:49:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-30-optimizing-page-load-time/</guid>
      <description>&lt;p&gt;Stumbled across this page today&amp;hellip;interesting discussion of how to bring
down page load times.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://www.die.net/musings/page_load_time/&#34;&gt;Optimizing Page Load Time&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Nice trick:  Free screen captures embedded in powerpoint</title>
      <link>https://emil.lerch.org/2006-10-24-nice-trick-free-screen-captures-embedded-in-powerpoint/</link>
      <pubDate>Tue, 24 Oct 2006 13:47:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-24-nice-trick-free-screen-captures-embedded-in-powerpoint/</guid>
      <description>&lt;p&gt;Step 1: Install &lt;a href=&#34;http://www.debugmode.com/wink/&#34;&gt;Wink&lt;/a&gt;&lt;br&gt;
Step 2: Use &lt;a href=&#34;http://www.amarasoftware.com/tutorials/importpowerpoint.html&#34;&gt;how to embed a Flash file into
Powerpoint&lt;/a&gt;
to get the video in powerpoint&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>When good ideas go horribly wrong</title>
      <link>https://emil.lerch.org/2006-10-12-when-good-ideas-go-horribly-wrong/</link>
      <pubDate>Thu, 12 Oct 2006 13:13:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-12-when-good-ideas-go-horribly-wrong/</guid>
      <description>&lt;p&gt;I just signed up for a service to connect me to Intel&amp;rsquo;s network while
out of town. I got 3 emails to confirm my service was active:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Email with my username&lt;/li&gt;
&lt;li&gt;Email with my password&lt;/li&gt;
&lt;li&gt;Email to state that my order was approved&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Good idea&amp;hellip;seperate the username email from the password email
(although a better idea would be to allow me to choose username and
password). However, email #3 included both my username and my password,
so a) what&amp;rsquo;s the point of the other 2 emails, and b) the security
advantage has been completely defeated.&lt;/p&gt;
&lt;p&gt;Think people&amp;hellip;think.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Tracking tool mashup</title>
      <link>https://emil.lerch.org/2006-10-11-tracking-tool-mashup/</link>
      <pubDate>Wed, 11 Oct 2006 11:55:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-11-tracking-tool-mashup/</guid>
      <description>&lt;p&gt;Here&amp;rsquo;s a &lt;a href=&#34;http://isnoop.net/tracking/&#34;&gt;neat little utility for tracking
packages&lt;/a&gt;. I like the touch of adding RSS
feeds. One small enhancement request&amp;hellip;at least with UPS you get an
expected delivery date. I assume many other companies provide that
information, yet this site doesn&amp;rsquo;t give that information.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Why should it be this hard?</title>
      <link>https://emil.lerch.org/2006-10-11-why-should-it-be-this-hard/</link>
      <pubDate>Wed, 11 Oct 2006 11:52:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-11-why-should-it-be-this-hard/</guid>
      <description>&lt;p&gt;Very cool article on attaining &lt;a href=&#34;http://engtech.wordpress.com/2006/08/11/the-holy-grail-of-synchronization-how-to-synchronize-microsoft-outlook-multiple-locations-google-calendar-gmail-ipod-and-mobile-phone-with-funambol-scheduleworld/&#34;&gt;the holy grail of
synchronization&lt;/a&gt;,
but why must it be that hard?&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Couple security things</title>
      <link>https://emil.lerch.org/2006-10-11-couple-security-things/</link>
      <pubDate>Wed, 11 Oct 2006 11:48:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-11-couple-security-things/</guid>
      <description>&lt;p&gt;I found a cool post from Scott Hanselmann the other day &lt;a href=&#34;http://www.hanselman.com/blog/2006ResolutionPrepare.aspx&#34;&gt;regarding
encrypted flash drives and password
lists&lt;/a&gt;. I&amp;rsquo;m
definitely using &lt;a href=&#34;http://www.truecrypt.org&#34;&gt;truecrypt&lt;/a&gt;, and I&amp;rsquo;m taking a
look at &lt;a href=&#34;http://www.pluralsight.com/tools.aspx&#34;&gt;password minder&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>PDF files</title>
      <link>https://emil.lerch.org/2006-10-06-pdf-files/</link>
      <pubDate>Fri, 06 Oct 2006 10:19:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-06-pdf-files/</guid>
      <description>&lt;p&gt;Couple cool pieces of free sofware to do PDF stuff:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.foxitsoftware.com&#34;&gt;Foxit reader&lt;/a&gt;. If you&amp;rsquo;re tired of
Adobe Reader&amp;rsquo;s slow load times, splash screens, intrusive update
process and desire to install a bunch of other crap on your system,
this is for you. My favorite feature (besides 1.5MB install and
super-fast load time) is the ability to annotate PDF documents
(highlight sections, strikeout, type onto forms that weren&amp;rsquo;t
designed for forms, etc.). There are some nits with the software,
but nothing worth going back to Adobe Reader for. Fantastic.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.primopdf.com/&#34;&gt;PrimoPDF&lt;/a&gt;. Installs a virtual printer
that creates a PDF file. File-&amp;gt;Print from any application and get
a PDF output. There&amp;rsquo;s a few other pieces of software that do the
same thing as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Foxit software also has a fairly cheap PDF editor even though you could
technically edit using the reader and the PrimoPDF tool. This makes me
wonder&amp;hellip;is the one of the main benefit for PDFs (non-editable content
without resorting to serious DRM technologies) starting to erode?&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Source code and search</title>
      <link>https://emil.lerch.org/2006-10-05-source-code-and-search/</link>
      <pubDate>Thu, 05 Oct 2006 10:50:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-05-source-code-and-search/</guid>
      <description>&lt;p&gt;It was only yesterday I ran across a &lt;a href=&#34;http://www.codase.com/&#34;&gt;search engine for source
code&lt;/a&gt;. Today, I find that &lt;a href=&#34;http://news.com.com/Google+crawls+into+source+code+search/2100-1024_3-6122819.html?tag=nl&#34;&gt;Google is entering
the same
market&lt;/a&gt;.
Cool.&lt;/p&gt;
&lt;p&gt;Oh, and here&amp;rsquo;s the link to the actual &lt;a href=&#34;http://www.google.com/codesearch&#34;&gt;Google search
engine&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>A few quick job links</title>
      <link>https://emil.lerch.org/2006-10-05-a-few-quick-job-links/</link>
      <pubDate>Thu, 05 Oct 2006 08:50:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-05-a-few-quick-job-links/</guid>
      <description>&lt;p&gt;Over the past few months I have thought quite a bit about resumes and
jobs outside Intel. A few quickie links for those that are looking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;http://www.lifehack.org/articles/lifehack/the-7-deadly-sins-of-resume-design.html&#34;&gt;Seven deadly sins of resume
design&lt;/a&gt;
(&lt;a href=&#34;http://www.lifehack.org&#34;&gt;lifehack.org&lt;/a&gt;, which is a pretty neat
resource itself)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.sao.org/&#34;&gt;Software Association of Oregon&lt;/a&gt;&amp;hellip;includes
link to
&lt;a href=&#34;http://www.oregonworks.org/jobs-interns.asp?page=welcome&#34;&gt;OregonWorks&lt;/a&gt;
for Jobs&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.lifehack.org/articles/lifehack/10-ways-to-tweak-your-tech-resume.html&#34;&gt;10 Ways to tweak your tech
resume&lt;/a&gt;,
also on lifehack.org, but referencing eWeek. However, I disagree
with the &amp;ldquo;lose the one page only rule&amp;rdquo; tip. When reading resumes I
literally throw out all but the first page. If the candidate doesn&amp;rsquo;t
engage me by the bottom of page one, it wasn&amp;rsquo;t meant to be. I&amp;rsquo;m not
completely clear how to reconcile that with the keyword problem,
though.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&amp;rsquo;s a ton of job web sites out there
(&lt;a href=&#34;http://www.hotjobs.com&#34;&gt;hotjobs&lt;/a&gt;, &lt;a href=&#34;http://www.monster.com&#34;&gt;monster&lt;/a&gt;,
&lt;a href=&#34;http://www.dice.com&#34;&gt;dice&lt;/a&gt;, etc.), but a quick google search will find
those pretty quickly.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Intel update, part deux</title>
      <link>https://emil.lerch.org/2006-10-04-intel-update-part-deux/</link>
      <pubDate>Wed, 04 Oct 2006 14:57:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-04-intel-update-part-deux/</guid>
      <description>&lt;p&gt;Well, I remain gainfully employed, although I will be doing something
different. What exactly, I&amp;rsquo;m not yet sure. What I do know is that every
manager from my formerly 3rd level manager down has been affected. One
layoff, 2 redeployments, one transition to non-manager employee, and one
move to another organization.&lt;/p&gt;
&lt;p&gt;So far, there are 28 people that I know that have been &amp;ldquo;redeployed&amp;rdquo;. It
doesn&amp;rsquo;t feel like I know 300 people at Intel to really make that a 10%
cut, but any way you cut it this is a difficult time. It&amp;rsquo;s Wednesday of
employee announcement week, but hopefully most everyone now knows their
fate and we can move on. Next week is re-organize week, so better (or at
least different) news is still to come.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Seriously?</title>
      <link>https://emil.lerch.org/2006-10-03-seriously/</link>
      <pubDate>Tue, 03 Oct 2006 17:47:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-10-03-seriously/</guid>
      <description>&lt;p&gt;I just went to find the phone number for a local Indian resteraunt
called the &lt;a href=&#34;http://www.curryleafpdx.com&#34;&gt;Curry Leaf&lt;/a&gt;. I found the phone
number on their web page, but also noticed that &lt;a href=&#34;http://curryleafpdx.blogspot.com/&#34;&gt;they have a
blog&lt;/a&gt;. Not sure what a resteraunt
needs with one&amp;hellip;I can see a chef, maybe, but just the resteraunt?&lt;/p&gt;
&lt;p&gt;Also enjoyed their &lt;a href=&#34;http://www.curryleafpdx.com/curryleaf/curry_new/contactus.html&#34;&gt;contact us
page&lt;/a&gt;,
which tells us what their website URL is. Of course if I&amp;rsquo;m on their web
site, wouldn&amp;rsquo;t I already know that?&lt;/p&gt;
&lt;p&gt;Hmm&amp;hellip;anyway, they have good Indian food. ;-)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Odd marketing move</title>
      <link>https://emil.lerch.org/2006-09-27-odd-marketing-move/</link>
      <pubDate>Wed, 27 Sep 2006 16:42:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-09-27-odd-marketing-move/</guid>
      <description>&lt;p&gt;I came home today to find a UPS 2nd day air envelope at my front door.
Not expecting anything, I went ahead and opened it up to find a letter
from Verizon trying to get me to sign up for their FIOS service. Well, I
already have FIOS, it works pretty well now that I think I have the
kinks out of it, and so I&amp;rsquo;m not sure it was worth the crazy amount it
probably cost to do a direct mail solicitation using UPS 2nd day air.&lt;/p&gt;
&lt;p&gt;Of course, I&amp;rsquo;m blogging about it, so maybe it wasn&amp;rsquo;t a bad move?&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>21 hours and counting</title>
      <link>https://emil.lerch.org/2006-09-27-21-hours-and-counting/</link>
      <pubDate>Wed, 27 Sep 2006 15:04:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-09-27-21-hours-and-counting/</guid>
      <description>&lt;p&gt;That&amp;rsquo;s how long it&amp;rsquo;s been since:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A router reboot&lt;/li&gt;
&lt;li&gt;An IP address change on Verizon&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I found out the other day that the reason my IP address kept changing
(and subsequently knocking me off the phone), was that I didn&amp;rsquo;t have
Verizon&amp;rsquo;s VDI-624 router as the first router in my setup. The VDI-624 is
the same as a DI-624 router from DLink but has specialized firmware for
Verizon. The interface looks the same, but they must truly have done
something different to it.&lt;/p&gt;
&lt;p&gt;Anyway, after fixing the IP address change problem, my old problem of
router reboots came back. I run both eMule and Bittorrent, and so I have
an insane number of connections open. For some odd reason, the router
could handle the connections when my Vonage router was closest to the
Internet (even though it was in the Vonage router&amp;rsquo;s DMZ), but not when
the VDI-624 is closest to the Internet. Odd. I didn&amp;rsquo;t really need
Bittorrent running so I shut it down completely and all is well,
although it would be nice to have everything running, so maybe I&amp;rsquo;ll play
with it another day. Not today. Today, everything is working as
designed. That&amp;rsquo;s a good thing.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Intel update</title>
      <link>https://emil.lerch.org/2006-09-25-intel-update/</link>
      <pubDate>Mon, 25 Sep 2006 09:07:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-09-25-intel-update/</guid>
      <description>&lt;p&gt;I usually try to refrain from posting work-related stuff since you
quickly start riding a fine line for what content is acceptable, but
today I&amp;rsquo;ll go ahead. We are (still) in the middle of our
&amp;ldquo;&lt;a href=&#34;http://www.intel.com/pressroom/archive/releases/20060905corp.htm&#34;&gt;restructuring&lt;/a&gt;&amp;rdquo;
(a.k.a. 10% downsizing). Quick update from my seat of visibility:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;April - first &amp;ldquo;efficiency&amp;rdquo; announcement internally and to wall
street. Announced to complete in 90 days (August).&lt;/li&gt;
&lt;li&gt;June/July timeframe: Number of &amp;ldquo;redeployments&amp;rdquo; affected several
groups. A few friends of mine were affected by this. Since this was
the first action, everyone I know was able to find another job
inside Intel.&lt;/li&gt;
&lt;li&gt;July: 1000 Manager reduction. My manager and my 3rd level manager
were both laid off.&lt;/li&gt;
&lt;li&gt;August: Announcement that analysis was complete on time, but
execution on that analysis would take until the end of the year.
Redeployments would occur in September.&lt;/li&gt;
&lt;li&gt;September: Redeployments will occur WW39 for managers and WW40 for
other employees. In normal terms, WW39 is the week beginning
September 25th and WW40 is the week beginning October 2nd.&lt;/li&gt;
&lt;li&gt;Last week: My admin was &amp;ldquo;redeployed&amp;rdquo; and I&amp;rsquo;m told that our group
will have a 20% reduction. Another group I work closely with will
have a 75% reduction.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll keep a lot of my analysis of this to myself for the time being, but
will mention that a redeployment at Intel means that you are given
several months where your job at Intel is literally to find another job
at Intel using our internal system. Alternatively, you can opt to leave
immediately and take a severance payment. There are some internal
positions open, but not many, so a redeployment effectively means a lay
off right now.&lt;/p&gt;
&lt;p&gt;For those reading that think that the process is very long, I agree, but
will offer that it appears as though management is trying to work the
logistics of the process in such a way as to make sure that people that
are affected are eligible for a few bonuses. If true, that&amp;rsquo;s a nice
silver lining to this particular cloud.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>That&#39;s a bold move...wonder how that&#39;ll work out?</title>
      <link>https://emil.lerch.org/2006-09-11-thats-a-bold-move-wonder-how-thatll-work-out/</link>
      <pubDate>Mon, 11 Sep 2006 12:27:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-09-11-thats-a-bold-move-wonder-how-thatll-work-out/</guid>
      <description>&lt;p&gt;So I&amp;rsquo;ve just finished making the switch for my default browser on my
work system. I wanted something other than Internet Explorer primarily
for tabbed browsing purposes, but secondarily to help reduce the amount
of memory that IE takes up.&lt;/p&gt;
&lt;p&gt;My first switch was to &lt;a href=&#34;http://www.maxthon.com/&#34;&gt;Maxthon&lt;/a&gt;, which
internally runs IE but handles tabbed browsing and &lt;a href=&#34;http://www.maxthon.com/features.htm&#34;&gt;mouse
gestures&lt;/a&gt;. I now pretty much expect
my browser to handle mouse gestures and get frustrated if holding the
right mouse button and moving down and then right doesn&amp;rsquo;t close the
tab.&lt;/p&gt;
&lt;p&gt;Maxthon was really very good, except that there was no plugin available
to handle &lt;a href=&#34;http://www.google.com/bookmarks/&#34;&gt;Google Bookmarks&lt;/a&gt;, which
solved another &lt;a href=&#34;http://emillerch.blogspot.com/2006/06/bookmark-nirvana.html&#34;&gt;thorny issue for
me&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Firefox sounded like a great solution except for the number of sites I
use (primarily inside Intel) that require ActiveX controls and other IE
specifics. I bumped across the solution to this recently when running
into an extension called &lt;a href=&#34;https://addons.mozilla.org/firefox/1419/&#34;&gt;IE
Tab&lt;/a&gt;. This allows you to load
IE within Firefox, mostly taking care of those issues. It hasn&amp;rsquo;t worked
with Test Director (and neither does their plugin from 1999), so I&amp;rsquo;m not
entirely off IE, but I&amp;rsquo;m nearly there.&lt;/p&gt;
&lt;p&gt;Oh, and NTLM authentication for the tons of Intel sites that need it?
Yeah, it&amp;rsquo;s &lt;a href=&#34;http://blogs.wdevs.com/shog9/archive/2005/03/09/2668.aspx&#34;&gt;got
that&lt;/a&gt;&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>On the topic of flash</title>
      <link>https://emil.lerch.org/2006-08-15-on-the-topic-of-flash/</link>
      <pubDate>Tue, 15 Aug 2006 15:07:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-08-15-on-the-topic-of-flash/</guid>
      <description>&lt;p&gt;So &lt;a href=&#34;http://emillerch.blogspot.com/2006/08/back-in-flash.html&#34;&gt;smaller files are better off on flash
memory&lt;/a&gt;? Small
files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Source code&lt;/li&gt;
&lt;li&gt;Most web stuff (HTML, CSS, JS, etc., etc.)&lt;/li&gt;
&lt;li&gt;Lots of miscellaneous docs (word, powerpoint, exel)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sounds to me like the best way to work on these things is to use flash
memory as your primary work area and copy the files to hard disk only as
backup. At least until &lt;a href=&#34;http://en.wikipedia.org/wiki/Hybrid_drive&#34;&gt;Hybrid hard
drives&lt;/a&gt; solve the problem&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Back in a flash</title>
      <link>https://emil.lerch.org/2006-08-15-back-in-a-flash/</link>
      <pubDate>Tue, 15 Aug 2006 12:02:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-08-15-back-in-a-flash/</guid>
      <description>&lt;p&gt;There&amp;rsquo;s a new performance feature in Windows Vista that has piqued my
curiosity. The feature is based on pre-fetching (where programs are
loaded into memory or in an area on your disk before you run it, because
the system knows you usually run the program).&lt;/p&gt;
&lt;p&gt;There are some other enhancements to the pre-fetch feature, but the
interesting one to me has been coined as
&lt;a href=&#34;http://articles.techrepublic.com.com/5100-10877_11-6060817.html&#34;&gt;ReadyBoost&lt;/a&gt;.
This feature uses flash memory (like a USB drive or compact flash card),
and loads the pre-fetch information on the memory. The concept is that
this will make your system faster.&lt;/p&gt;
&lt;p&gt;Knowing that USB drives are slower than hard drives, I found the claim
curious. It turns out that the way they access the data must be in
smaller chunks. While flash memory is slower, the access time is much
faster. There are several assumptions that vary the analysis fairly
significantly, but my most pessimistic assumptions say that any file
smaller than 100k is better off on flash memory. My most optimistic
assumptions say that any file smaller than 500k is better off. Over
those sizes, a hard drive will be faster (assuming that you keep your
disk &lt;a href=&#34;http://en.wikipedia.org/wiki/Defragmentation&#34;&gt;defragmented&lt;/a&gt;).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Your data...available and protected</title>
      <link>https://emil.lerch.org/2006-08-11-your-data-available-and-protected/</link>
      <pubDate>Fri, 11 Aug 2006 17:21:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-08-11-your-data-available-and-protected/</guid>
      <description>&lt;p&gt;So I made the plunge and purchased a
&lt;a href=&#34;http://emillerch.blogspot.com/2006/05/is-it-really-that-much-to-ask-for.html&#34;&gt;NAS&lt;/a&gt;.
I got a &lt;a href=&#34;http://www.buffalotech.com&#34;&gt;Buffalo Technology&lt;/a&gt; product, but
not the &lt;a href=&#34;http://www.buffalotech.com/products/product-detail.php?productid=131&amp;amp;categoryid=28&#34;&gt;one that I previously
mentioned&lt;/a&gt;.
The difference between the Linkstation Home Server and the Gigabit
Linkstation is DLNA certification for media streaming to a very few
certified players, which seems to add about $75 to the cost. In 5 years
I may regret it, but right now, I&amp;rsquo;m pretty happy with the
&lt;a href=&#34;http://www.buffalotech.com/products/product-detail.php?productid=106&amp;amp;categoryid=22&#34;&gt;HD-HG300LAN&lt;/a&gt;.
I&amp;rsquo;m also somewhat future-proofed with the Gigabit option.&lt;/p&gt;
&lt;p&gt;I moved all the data from my external USB drive and scheduled backups
onto the USB drive (my external USB is now strictly a backup device). So
far, so good. The downside is that the data transfer had to occur over
the network since the NAS web interface doesn&amp;rsquo;t allow any file
manipulation. The firmware (v1.42) is pretty rough around the edges, and
setting the security up hasn&amp;rsquo;t worked so well, but everything else has
worked as advertised, and &lt;a href=&#34;http://kathrynmarie.shutterfly.com&#34;&gt;pictures of
Kathryn&lt;/a&gt; are now backed up.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>It&#39;s been bugging me</title>
      <link>https://emil.lerch.org/2006-08-10-its-been-bugging-me/</link>
      <pubDate>Thu, 10 Aug 2006 14:04:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-08-10-its-been-bugging-me/</guid>
      <description>&lt;p&gt;I had seen a website that contained a list of login credentials for many
of the &amp;ldquo;free but registration required&amp;rdquo; web sites. After more googling
than I really would care for, I found it.
&lt;a href=&#34;http://www.bugmenot.com&#34;&gt;www.bugmenot.com&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>dotnetnuke on GoDaddy</title>
      <link>https://emil.lerch.org/2006-08-08-dotnetnuke-on-godaddy/</link>
      <pubDate>Tue, 08 Aug 2006 12:25:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-08-08-dotnetnuke-on-godaddy/</guid>
      <description>&lt;p&gt;In my last post I mentioned bethanycommunity.net web site. In getting it
working, I had to get &lt;a href=&#34;http://www.dotnetnuke.com&#34;&gt;dotnetnuke&lt;/a&gt; installed
and running on the root directory of the site. Doing this on
&lt;a href=&#34;http://www.godaddy.com&#34;&gt;godaddy&lt;/a&gt; was quite an interesting feat and
required some changes to the dotnetnuke source code. I hope their latest
version helps fix that problem. There are some instructions on doing
this &lt;a href=&#34;http://www.northernstarsolutions.com/Default.aspx?tabid=61&#34;&gt;here&lt;/a&gt;.
I found they got me close, but the installer was still a problem, and
hence the (temporary) source code changes to skip over some of DNN&amp;rsquo;s
more dangerous install behavior.&lt;/p&gt;
&lt;p&gt;Some people think that this is GoDaddy&amp;rsquo;s problem and people should be
able to create very insecure sites, but I side with GoDaddy that
normally applications should not need to update their core configuration
files and place files in their production directories.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Local issues</title>
      <link>https://emil.lerch.org/2006-08-08-local-issues/</link>
      <pubDate>Tue, 08 Aug 2006 12:17:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-08-08-local-issues/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve volunteered some time to get a website up and running for a local
organization. The Bethany Coalition was renamed to the &lt;a href=&#34;http://www.bethanycommunity.net&#34;&gt;Bethany
Community Network&lt;/a&gt;, and was originally
formed to help out with planning to accomodate more people moving into
the area. Portland has an urban growth boundary that helps to confine
population and prevent urban sprawl. In our area, the boundary was
expanded to enable more people, but that means new infrastructure needs
to be in place. You can read more about it on their (my) web site. Feel
free to comment or email me directly with suggestions for improvement.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Windows annoyances</title>
      <link>https://emil.lerch.org/2006-08-08-windows-annoyances/</link>
      <pubDate>Tue, 08 Aug 2006 09:05:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-08-08-windows-annoyances/</guid>
      <description>&lt;p&gt;Why is it that after roughly &lt;a href=&#34;http://en.wikipedia.org/wiki/Windows_nt&#34;&gt;18 years of work on the Windows NT family
of operating systems&lt;/a&gt;, Windows
XP still forgets my desktop background picture?&lt;/p&gt;
&lt;p&gt;As I had deleted the original file, I had to go spelunking today to
figure out that it&amp;rsquo;s copied to C:\Documents and
Settings\[username]\Local Settings\Application
Data\Microsoft\Wallpaper 1.bmp. The most frustrating part is that I
could see the picture, but only when logging out/shutting down.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Ruby on Rails?</title>
      <link>https://emil.lerch.org/2006-08-02-ruby-on-rails/</link>
      <pubDate>Wed, 02 Aug 2006 08:18:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-08-02-ruby-on-rails/</guid>
      <description>&lt;p&gt;I got a chance to check out &lt;a href=&#34;http://rubyonrails.org/&#34;&gt;Ruby on Rails&lt;/a&gt; the
other day. That basically explains it. Rails is a set of libraries that
use the programming language Ruby to make web programming very, very
productive. I&amp;rsquo;ve heard of defections to RoR from .NET and Java. I&amp;rsquo;m sure
there are some, but the stories appear to be anecdotal. Anyway, it looks
promising, and I hope the competition helps improve the productivity of
all languages&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>ObjectDataSource limitations</title>
      <link>https://emil.lerch.org/2006-08-02-objectdatasource-limitations/</link>
      <pubDate>Wed, 02 Aug 2006 08:12:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-08-02-objectdatasource-limitations/</guid>
      <description>&lt;p&gt;One of the more exciting things in Visual Studio 2005 for me was the
idea of an object data source for grids. Well, I&amp;rsquo;m not doing a lot of
hands on programming right now, and I finally got around to using it.
I&amp;rsquo;m not that thrilled. Here&amp;rsquo;s a snippet from a &lt;a href=&#34;http://msdn2.microsoft.com/en-us/library/ms227436.aspx&#34;&gt;help
file&lt;/a&gt; that is my
current source of frustration:&lt;br&gt;
&lt;!-- raw HTML omitted --&gt;&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;The &lt;strong&gt;ObjectDataSource&lt;/strong&gt; control
will create an instance of the source object, call the specified
method, and dispose of the object instance all within the scope of a
single request, if your object has instance methods instead of
&lt;strong&gt;static&lt;/strong&gt; methods (&lt;strong&gt;Shared&lt;/strong&gt; in Visual Basic). Therefore, your
object must be stateless. That is, your object should acquire and
release all required resources within the span of a single
request.&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This pretty much means that they expect an object to be a pass through
to some other data source (XML, DB, etc.). I&amp;rsquo;m just capturing some
session information right now. I find it easier to create/populate an
object and databind directly then try to make an object that&amp;rsquo;s specific
to this problem. I think for the time being I&amp;rsquo;ll go that route&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Efficient Markets?</title>
      <link>https://emil.lerch.org/2006-08-02-efficient-markets/</link>
      <pubDate>Wed, 02 Aug 2006 07:58:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-08-02-efficient-markets/</guid>
      <description>&lt;p&gt;I don&amp;rsquo;t think so. The &lt;a href=&#34;http://en.wikipedia.org/wiki/Efficient_market_theory&#34;&gt;theory/hypothesis is full of
crap&lt;/a&gt;. We learned
this in our finance classes in our MBA, but I don&amp;rsquo;t buy it. There are
several versions of it, but even the most weak of the arguments claims
that technical analysis won&amp;rsquo;t work. From what I&amp;rsquo;ve studied of technical
analysis, it&amp;rsquo;s trying to use numbers and history to predict aggregate
investor psychology. A simple form would be that if a stock is at it&amp;rsquo;s
52 week high, more investors think it&amp;rsquo;s time to take profits than
investors think it&amp;rsquo;s time to get into the stock, so the price will
probably go down.&lt;/p&gt;
&lt;p&gt;Efficient theory claims the market is a machine that absorbs information
and adjusts the prices within seconds to account for that information.
Nothing else matters. I think the market is made up of people, and
people have feelings, and stocks become either overhyped or forgotten.
And when that stock hits it&amp;rsquo;s 52 week high, I know I start to consider
taking my money off the table&amp;hellip;&lt;/p&gt;
&lt;p&gt;I like &lt;a href=&#34;http://en.wikipedia.org/wiki/Warren_Buffett&#34;&gt;Warren Buffett&amp;rsquo;s&lt;/a&gt;
feeling on the matter (which comes from &lt;a href=&#34;http://en.wikipedia.org/wiki/Benjamin_Graham&#34;&gt;Benjamin
Graham&lt;/a&gt;). Take a look at
&lt;a href=&#34;http://www.fool.com/news/commentary/2006/commentary06072201.htm&#34;&gt;this
commentary&lt;/a&gt;
also.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Mad money?</title>
      <link>https://emil.lerch.org/2006-08-02-mad-money/</link>
      <pubDate>Wed, 02 Aug 2006 07:50:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-08-02-mad-money/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve found &lt;a href=&#34;http://en.wikipedia.org/wiki/Mad_Money&#34;&gt;Mad Money&lt;/a&gt; to be an
interesting and entertaining show. I am watching more regularly now that
my daughter has arrived and I need something to do while she sleeps in
my arms, and with the summer here, this is not bad. Anyway, I don&amp;rsquo;t
always agree with &lt;a href=&#34;http://en.wikipedia.org/wiki/James_Cramer&#34;&gt;Cramer&lt;/a&gt;
(the host), but where I have agreed and taken action on his
recommendations, it&amp;rsquo;s worked out well. The latest trade is &lt;a href=&#34;http://finance.yahoo.com/q?d=t&amp;amp;s=VLO&#34;&gt;Valero
Energy (VLO)&lt;/a&gt;, bought before Q2
earnings announcement yesterday. Earnings came out as (Cramer) expected.
Yesterday wasn&amp;rsquo;t a good day for the market, but today both the market
and Vallero seems to be heading in the right direction&amp;hellip;&lt;/p&gt;
&lt;p&gt;I recommend the show, but only if combined with your own research and
analysis. Make sure you&amp;rsquo;re not &amp;ldquo;drinking the Kool-aid&amp;rdquo;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Sometimes, it&#39;s not what you know...</title>
      <link>https://emil.lerch.org/2006-07-31-sometimes-its-not-what-you-know/</link>
      <pubDate>Mon, 31 Jul 2006 09:55:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-07-31-sometimes-its-not-what-you-know/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;http://spirko.blogspot.com&#34;&gt;Jeff&lt;/a&gt; told me he was going to blog about
this, but it doesn&amp;rsquo;t look like he&amp;rsquo;s done it yet, so I will. Jeff has
been looking for a notebook recently, and since I work for Intel I&amp;rsquo;m
pretty close to that particular market. I&amp;rsquo;m also working on the
&lt;a href=&#34;http://www.intel.com/cd/channel/reseller/emea/eng/283358.htm&#34;&gt;Verified By
Intel®&lt;/a&gt;
program, which deals directly with the notebook market, so I&amp;rsquo;m pretty
clued in.&lt;/p&gt;
&lt;p&gt;So Jeff emails me on July 3rd to ask me what the T2050 processor is. He
found a notebook that includes the processor, and can&amp;rsquo;t find any
information on Intel.com&amp;hellip;I verify his results, find &lt;a href=&#34;http://en.wikipedia.org/wiki/List_of_Intel_Core_microprocessors&#34;&gt;processor
information on
wikipedia&lt;/a&gt;,
and then the fun begins. I can&amp;rsquo;t find information anywhere internally,
which is a huge problem for me because if I can&amp;rsquo;t find information, that
means that Intel can&amp;rsquo;t process returns on the product and there is no
support documentation on the product.&lt;/p&gt;
&lt;p&gt;Jeff emails customer support, and after being routed around (support is
looking at the same things I am, so they&amp;rsquo;re running into the same
problem), basically gets back a non-response around July 11. I have a
few internal contacts that Jeff doesn&amp;rsquo;t have access to, so I email them,
but Intel is in the middle of a &lt;a href=&#34;http://www.intel.com/core2duo/index.htm?iid=HMPAGE+Feature_06ww30&#34;&gt;major
launch&lt;/a&gt;
at this time, so I don&amp;rsquo;t get back any response. Finally I hear back on
July 25th, and the information I get back is somewhat disturbing. I find
that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The processor is &amp;ldquo;off roadmap&amp;rdquo;, meaning that it wasn&amp;rsquo;t planned at
all, but rather put onto the market at the last minute, without any
supporting documentation.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s OEM-only (I support &amp;ldquo;boxed products&amp;rdquo; - these are products that
you can buy in a box online or through a retailer). Being OEM only,
you can only get the processor if it&amp;rsquo;s already in a computer. This
is why it&amp;rsquo;s not in my tool for returns, but it still should be in
for technical support&amp;hellip;&lt;/li&gt;
&lt;li&gt;The person responsible for worldwide support of the processor didn&amp;rsquo;t
know anything about the processor until he bumped into one at
Circuit City.&lt;/li&gt;
&lt;li&gt;This apparently happens all the time with desktop processors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What a way for a company to support its products. Hopefully the &lt;a href=&#34;http://www.informationweek.com/news/showArticle.jhtml?articleID=190400148&#34;&gt;drive
to
efficiency&lt;/a&gt;
will help&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Long blog posts</title>
      <link>https://emil.lerch.org/2006-07-26-long-blog-posts/</link>
      <pubDate>Wed, 26 Jul 2006 15:53:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-07-26-long-blog-posts/</guid>
      <description>&lt;p&gt;I generally dislike really long blog posts. I think my last two were too
long. So this one won&amp;rsquo;t be.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Software Update annoyances</title>
      <link>https://emil.lerch.org/2006-07-26-software-update-annoyances/</link>
      <pubDate>Wed, 26 Jul 2006 15:37:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-07-26-software-update-annoyances/</guid>
      <description>&lt;p&gt;My last post also reminded me that I&amp;rsquo;ve been meaning to post on another
subject - that of software updates. These are (sometimes) necessary and
usually desirable functionality, as long as they don&amp;rsquo;t get in the way of
the user trying to do their job. As mentioned previously, I think Adobe
does a great job showing how &lt;strong&gt;not&lt;/strong&gt; to do this (modal dialog box, even
when viewing in a browser, usually asks to install a bunch of unneeded
crap, etc.).&lt;/p&gt;
&lt;p&gt;One thing I see consistently done wrong in these update programs is the
fact that they tend to ask you to update when you start the program. If
a user started a program, do you think they want to update the program,
or use the program? (that&amp;rsquo;s rhetorical) Unless the update is critical,
the update process should ask to update the program and/or its
components when the user requests shutdown of the program. Here&amp;rsquo;s the
complete guidelines that I think should be followed:&lt;/p&gt;
&lt;p&gt;If the update isn&amp;rsquo;t critical, wait until shutdown to request the update.
&amp;ldquo;Critical&amp;rdquo; updates include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Security-related issues that can expose the user to remote attack&lt;/li&gt;
&lt;li&gt;Stability issues that the user has experienced. You would know this
only if a crash reporting facility exists and the user has opted
into the use of the facility.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If critical, state so and offer the update on application start&lt;/p&gt;
&lt;p&gt;On shutdown, ask the user to update, but close the application if no
reponse has been received after some time (how long?)&lt;/p&gt;
&lt;p&gt;Only include updates for existing components - don&amp;rsquo;t ask the user to
install Google Toolbar (unless of course the application that they&amp;rsquo;re
updating is Google Toolbar)&lt;/p&gt;
&lt;p&gt;Allow the user to choose which components to update. It&amp;rsquo;s ok to use an
opt-out model for this. Include a &amp;ldquo;don&amp;rsquo;t ask me again&amp;rdquo; facility along
with a way for the user to go back and install the &amp;ldquo;don&amp;rsquo;t ask me again&amp;rdquo;
updates (e.g. through a specific update application menu item)&lt;/p&gt;
&lt;p&gt;Try to avoid forcing an application restart. If you&amp;rsquo;re shutting down the
application it may not be a big deal, but if the user selects that menu
item or the update is critical, this becomes important. It&amp;rsquo;s really
important if the application is a service or an operating system. Yes,
Microsoft does a horrible job at this&amp;hellip;&lt;/p&gt;
&lt;p&gt;If you have multiple applications in your company, use a consistent
service that can update all applications. I do give credit to Microsoft
on this one as they are working on getting a single update service.
Ideally, there would be a single service/paradigm used across companies
(e.g. MSI/Installshield for installations). Market opportunity?&lt;/p&gt;
&lt;p&gt;Of course, I could be wrong, but I don&amp;rsquo;t think I am. Let me know.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Programming that screws things up</title>
      <link>https://emil.lerch.org/2006-07-26-programming-that-screws-things-up/</link>
      <pubDate>Wed, 26 Jul 2006 14:22:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-07-26-programming-that-screws-things-up/</guid>
      <description>&lt;p&gt;As I am waiting to respond to a friend of mine today because my browser
won&amp;rsquo;t respond, I&amp;rsquo;m reminded of a few really stupid things that hopefully
will become less common in software in the future.&lt;/p&gt;
&lt;p&gt;The specific incident today was that I wanted to click on a link
(Outback Steakhouse Menu), but the browser stopped responding. Was my
computer slow? No. Was it doing something else to the point where it
couldn&amp;rsquo;t handle my command? No. What happened was that I had been
downloading a 50MB file from a web site. The other browser window just
finished downloading the file&amp;hellip;so the last step in the process is to
copy the file from the temporary directory to the final download
directory. During that step, apparently all other browser windows stop
operating.&lt;/p&gt;
&lt;p&gt;These issues make computing frustrating, and to the casual user, makes
the computer &amp;ldquo;slow&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Other annoyances:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Modal dialog boxes. These are barely tolerable under normal
circumstances, but are horrible in applications with multiple
windows when the modal dialog box requires input before any
operation can commence in any window. Adobe Acrobat does a fantastic
job with this one&amp;hellip;they have a window come up for their product
updates that sometimes pops under the browser when you view a PDF
through the browser. It can hang Firefox and can appear to hang
Internet Explorer.&lt;/li&gt;
&lt;li&gt;Waiting to have it &amp;ldquo;perfect&amp;rdquo; before displaying results. Windows
explorer is a good example - some directories will take 30 seconds
or more because I have large files and it wants to do something (to
prepare thumbnails maybe?). I don&amp;rsquo;t even need the icons right away -
just show me the file names and fill the rest in on your own time!&lt;/li&gt;
&lt;li&gt;Lack of user feedback. This just happened to me also. Click on a
link (Quicktime movie) and nothing appears to happen. It&amp;rsquo;s actually
downloading but I don&amp;rsquo;t have any feedback, so I click it again, and
the download restarts, but I am completely unaware of this. I see
others struggle with this one. My favorite is when someone launches
a really big application but there&amp;rsquo;s no feedback, so they launch it
again, and repeat until eventually the computer brings up the
application, but then brings up like 10 more windows and is
completely choking on memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other examples aren&amp;rsquo;t coming to me, but I&amp;rsquo;ll try to post when I run
across one. Feel free to leave comments with other good annoyances that
appear to slow/stall the computer, but really aren&amp;rsquo;t.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>IPOs - not hard to call?</title>
      <link>https://emil.lerch.org/2006-07-14-ipos-not-hard-to-call/</link>
      <pubDate>Fri, 14 Jul 2006 10:07:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-07-14-ipos-not-hard-to-call/</guid>
      <description>&lt;p&gt;Maybe this is just a recent phenominon, but I&amp;rsquo;ve been watching and/or
participating in IPO investing (sometimes trading) for the last year or
so, and have a 100% success rate in making decent money. We&amp;rsquo;ve been
selective, but made investments in Under Armour (UARM), Master Card
(MA), and thought really hard about Sealy (ZZ) and Burger King (BKC).
Here&amp;rsquo;s the formula I&amp;rsquo;ve (we&amp;rsquo;ve) been using:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Absolutely &lt;strong&gt;must&lt;/strong&gt; be a well-known company. If the company name
isn&amp;rsquo;t recognized, then I don&amp;rsquo;t usually give more than a cursory
glance at the rest.&lt;/li&gt;
&lt;li&gt;Business model needs to be solid. We passed on Vonage (VG) because
they have little to offer customers to stay with them, and the big
incumbants can (and are starting to) roll out similar services as
comparable price points. There&amp;rsquo;s no reason for customers to stay.&lt;/li&gt;
&lt;li&gt;The wife needs to agree. Not to be flippant here&amp;hellip;a second or third
person brings a different perspective and will often kill some
reasonable concepts (in our case Sealy and Burger King).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;UARM and MA were both fairly short term investments, and you can
certainly argue with me there. UARM was held for about a quarter until I
got sufficiently concerned about Nike coming in to destroy them.&lt;/p&gt;
&lt;p&gt;MA is a decent long-term investment, but I had to go on margin to buy
the stock, so I sold after a month and having made some significant
money. Even without the margin issue, our #1 rule has been &amp;ldquo;don&amp;rsquo;t get
greedy&amp;rdquo;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>When technology and consumers work well</title>
      <link>https://emil.lerch.org/2006-07-06-when-technology-and-consumers-work-well/</link>
      <pubDate>Thu, 06 Jul 2006 18:32:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-07-06-when-technology-and-consumers-work-well/</guid>
      <description>&lt;p&gt;Now that I have a &lt;a href=&#34;http://kellyemil.blogspot.com/2006/06/kathryn-marie-sloan-lerch-born-454pm.html&#34;&gt;fantastic
reason&lt;/a&gt;
to use the new camcorder, I have been making some recordings and
otherwise playing with the features. I recently posted &lt;a href=&#34;http://kellyemil.blogspot.com/2006/07/for-true-die-hard-kathryn-fans.html&#34;&gt;two
videos&lt;/a&gt;
to &lt;a href=&#34;http://www.youtube.com&#34;&gt;Youtube.com&lt;/a&gt;, and the front-to-back process
was really straight-forward&amp;hellip;technology should continue to put
consumers at the center of their world like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Plug camcorder into computer, turn it on&lt;/li&gt;
&lt;li&gt;Load Windows Movie Maker&lt;/li&gt;
&lt;li&gt;Tell WMM to import video from the camcorder&lt;/li&gt;
&lt;li&gt;WMM detects the clips on the camcorder&lt;/li&gt;
&lt;li&gt;Take each clip you want to save, save the file as a seperate WMV&lt;/li&gt;
&lt;li&gt;Upload WMV file to YouTube&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While the above process is really cool, here&amp;rsquo;s how I could see it
optimized even further:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Plug camcorder into computer, turn it on&lt;/li&gt;
&lt;li&gt;Load Windows Movie Maker&lt;/li&gt;
&lt;li&gt;Tell WMM to import video from the camcorder&lt;/li&gt;
&lt;li&gt;WMM detects clips, but also stops when hitting the end of the tape
that&amp;rsquo;s been recorded on&lt;/li&gt;
&lt;li&gt;Take each clip you want to post, post the file (menu item/context
menu) directly to YouTube - if login required, WMM can request, or
integrate with the upcoming
&lt;a href=&#34;http://msdn.microsoft.com/webservices/infocard/default.aspx&#34;&gt;Infocard&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>When technology and consumers collide</title>
      <link>https://emil.lerch.org/2006-07-06-when-technology-and-consumers-collide/</link>
      <pubDate>Thu, 06 Jul 2006 18:29:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-07-06-when-technology-and-consumers-collide/</guid>
      <description>&lt;p&gt;So I ran into a really weird situation with the camcorder I recently
purchased. The camcorder has two data ports, one is
&lt;a href=&#34;http://en.wikipedia.org/wiki/Firewire&#34;&gt;Firewire&lt;/a&gt;, and one is
&lt;a href=&#34;http://en.wikipedia.org/wiki/USB&#34;&gt;USB&lt;/a&gt;. The camera itself can do three
data-related things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Transfer recorded video to the computer&lt;/li&gt;
&lt;li&gt;Transfer pictures to the computer&lt;/li&gt;
&lt;li&gt;Use camcorder as a web cam&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In order to do #1, you need to use the Firewire port. This seems
logical but a bit antiquated. Originally, Firewire could handle speeds
of 400Mbps where USB could only handle 12Mbps. Apparently Sony decided
it was easier to force folks to use the Firewire port than to have to
explain the differences between USB 1.x and USB 2.0, which can handle
speeds of 480Mbps.&lt;/p&gt;
&lt;p&gt;All fine so far. I bought the firewire card and cable and installed it
into my computer. Now here&amp;rsquo;s where things get weird. Firewire does
&lt;strong&gt;not&lt;/strong&gt; allow you to do #2 and #3 above. So now I need to actually use
&lt;strong&gt;both&lt;/strong&gt; the firewire and the USB cables in the computer if I want to do
all three operations. How weird. Maybe Sony is trying to transition off
Firewire in favor of USB?&lt;/p&gt;
&lt;p&gt;Whatever the reason, it&amp;rsquo;s a very awkward customer experience, and I
personally think they should stick with USB only and suck up having to
explain USB 1.x vs. USB 2.0. I would think they&amp;rsquo;d enhance the customer
experience while reducing both manufacturing and support costs.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Bookmark nirvana</title>
      <link>https://emil.lerch.org/2006-06-23-bookmark-nirvana/</link>
      <pubDate>Fri, 23 Jun 2006 13:21:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-06-23-bookmark-nirvana/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m finally getting bookmarks under control, and I can&amp;rsquo;t believe how
much better life is. When you use Internet Explorer for stuff that
doesn&amp;rsquo;t like Firefox (and for most things work-related), and Firefox for
normal browsing, and you have multiple computers, things get pretty
unweildy. Now, thanks to &lt;a href=&#34;http://www.google.com/bookmarks&#34;&gt;Google&lt;/a&gt;, my
problem is mostly solved.&lt;/p&gt;
&lt;p&gt;There are several bookmark managers out there, but only Google Bookmarks
seems to have solved my main problem and has support for both Internet
Explorer and Firefox. Here are the caveats:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Internet explorer support is through a &lt;a href=&#34;http://www.google.com/tools/toolbar/T4/index.html&#34;&gt;beta version of the Google
toolbar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The Google toolbar for Firefox is still back a few revisions, so I
need a &lt;a href=&#34;https://addons.mozilla.org/firefox/2453/&#34;&gt;third party firefox
extension&lt;/a&gt; to support it.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.maxthon.com/&#34;&gt;Maxthon&lt;/a&gt;, the software I use to get
Internet Explorer in tabs until &lt;a href=&#34;http://www.microsoft.com/windows/ie/default.mspx&#34;&gt;IE
7&lt;/a&gt; comes out,
doesn&amp;rsquo;t support the google toolbar, and there&amp;rsquo;s no good addon for
Maxthon directly. I&amp;rsquo;m starting to use IE (without Maxthon) a fair
amount now, and it&amp;rsquo;s the one big downside to this bookmark thing.&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Questions and Answers are like footnotes in annual reports</title>
      <link>https://emil.lerch.org/2006-06-13-questions-and-answers-are-like-footnotes-in-annual-reports/</link>
      <pubDate>Tue, 13 Jun 2006 08:58:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-06-13-questions-and-answers-are-like-footnotes-in-annual-reports/</guid>
      <description>&lt;p&gt;I was watching a webcast the other day (topic irrelevant) and it
occurred to me that what I really like about webcasts and earnings
conference calls is the Q&amp;amp;A section at the end. All the pre-canned stuff
in the beginning can be so filled with political spin it&amp;rsquo;s hard to get a
handle on what&amp;rsquo;s really going on. Q&amp;amp;A (either participating in or
listening to) provides the opportunity to cut through some of that and
get the real information.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Outlook 12 goodness</title>
      <link>https://emil.lerch.org/2006-06-13-outlook-12-goodness/</link>
      <pubDate>Tue, 13 Jun 2006 08:54:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-06-13-outlook-12-goodness/</guid>
      <description>&lt;p&gt;I haven&amp;rsquo;t actually used Outlook 12, but I just read a short list of Omar
Shahine&amp;rsquo;s &lt;a href=&#34;http://blogs.msdn.com/melissamacbeth/archive/2006/02/17/534491.aspx&#34;&gt;favorite
features&lt;/a&gt;.
I look forward to the new features (there is also a document
auto-preview that looks cool but I really look forward to figuring out
how it&amp;rsquo;s breaking my system).&lt;/p&gt;
&lt;p&gt;I find the color categories feature strangely familiar from
gmail&amp;hellip;maybe they&amp;rsquo;ll incorporate that feature into Windows Live and
really copy Gmail. ;-)&lt;/p&gt;
&lt;p&gt;In MS&amp;rsquo;s defense, at least they&amp;rsquo;re not too proud to copy features (or buy
companies) when they&amp;rsquo;re out-innovated.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Camcorder purchase</title>
      <link>https://emil.lerch.org/2006-06-05-camcorder-purchase/</link>
      <pubDate>Mon, 05 Jun 2006 13:45:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-06-05-camcorder-purchase/</guid>
      <description>&lt;p&gt;I just purchased a &lt;a href=&#34;http://www.amazon.com/gp/product/B000E0DU62/ref=pd_cp_p_title/104-3040708-0690318?%5Fencoding=UTF8&amp;amp;v=glance&amp;amp;n=502394&#34;&gt;Sony
HC36&lt;/a&gt;
and documented the experience for a couple friends, so I thought I&amp;rsquo;d
share it here. Here’s the lowdown on the process (skip to step 9 if you
want to skip how I figured out what camcorder to buy).&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;http://consumerreports.org/&#34;&gt;Consumerreports.org&lt;/a&gt; led me to a JVC
camcorder, and any one of several Sony camcorders. Sony is the best
overall brand, while a couple individual JVC, Panasonic, and Cannon
camcorders rate well.&lt;/p&gt;
&lt;p&gt;I looked at Amazon to confirm the JVC camcorder, and was surprised to
find lots of poor reviews for it. I eliminated it as a candidate.&lt;/p&gt;
&lt;p&gt;Took a look at CNET.com for the other camcorders. They had some
reasonable advice, but being a tech-focused review site, they were
pretty much only interested in “the best of the best of the best –
sir!”. Their reviews basically pointed to buying the most expensive
camcorder (like the Sony High-Def camcorder for $3600). I was able to at
least discern some of the differences between the Sony units from CNET.&lt;/p&gt;
&lt;p&gt;Narrowed search down to Sony basically due to a) price (this eliminated
the Panasonic) and b) a few features (like webcam abilities that
eliminated the cannon).&lt;/p&gt;
&lt;p&gt;Googled for the differences between models and determined the
following:&lt;/p&gt;
&lt;!-- raw HTML omitted --&gt;
&lt;p&gt;Of these 6 models, I determined that there is very little difference in
the 2x/3x series. The 42/46 is significantly ($100) more expensive and
has better technical specs, but the 2x/3x differs in just a few features
and the core video capture capability is the same.&lt;/p&gt;
&lt;p&gt;The 32 was cheapest on Amazon and I tried to do better on eBay, but was
unsuccessful.&lt;/p&gt;
&lt;p&gt;The deal I saw on Amazon went away and a friend pointed me to
&lt;a href=&#34;http://pricescan.com/&#34;&gt;Pricescan.com&lt;/a&gt;. The price graph on the 32 showed
a spike in prices in the current week, confirming prices were up overall
on the 32.&lt;/p&gt;
&lt;p&gt;Based on pricescan, the prices between the 32 and 36 were pretty much
even, so I looked around various sites (Amazon,
&lt;a href=&#34;http://buy.com/&#34;&gt;buy.com&lt;/a&gt;, &lt;a href=&#34;http://overstock.com/&#34;&gt;overstock.com&lt;/a&gt;,
&lt;a href=&#34;http://outpost.com/&#34;&gt;outpost.com&lt;/a&gt;,
&lt;a href=&#34;http://shopping.yahoo.com/&#34;&gt;shopping.yahoo.com&lt;/a&gt;) to try to beat
pricescan. &lt;strong&gt;I was able to beat&lt;/strong&gt;
&lt;a href=&#34;http://pricescan.com/&#34;&gt;&lt;strong&gt;pricescan.com&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;, but only by $2.50.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I used &lt;a href=&#34;http://shopping.yahoo.com/&#34;&gt;shopping.yahoo.com&lt;/a&gt; to check out
merchant reviews and make a selection. The second best price came from
the lowest price vendor on &lt;a href=&#34;http://pricescan.com/&#34;&gt;pricescan.com&lt;/a&gt;. They
had a good review and Yahoo buyer protection and I was going to go with
them until their website had a technical difficulty. I went to the
cheapest vendor (4.5 out of 5 stars, but no Yahoo buyer protection) and
placed the order there.&lt;/p&gt;
&lt;p&gt;I needed a set of accessories, and already knew where to get the best
deals, so I went straight to &lt;a href=&#34;http://newegg.com/&#34;&gt;newegg.com&lt;/a&gt;. I already
had a DVD burner and media, but I ordered (total cost ~$75):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firewire card for the computer&lt;/li&gt;
&lt;li&gt;Firewire cable (6 pin to 4 pin)&lt;/li&gt;
&lt;li&gt;25 pack DVD movie cases&lt;/li&gt;
&lt;li&gt;512MB memory stick pro duo (I hate how Sony has proprietary memory
formats).&lt;/li&gt;
&lt;li&gt;6 pack of mini-DV tape&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I put together a &lt;a href=&#34;https://secure.newegg.com/NewVersion/WishList/MySavedWishDetail.asp?ID=3487544&#34;&gt;saved wish
list&lt;/a&gt;
at newegg.com that includes what I ordered as well as the internal DVD
burner and media I would order today if I needed them. If you need the
external version of the burner it’s probably another $10-$15. Also note
that the memory is optional – as far as I know, the camera only uses the
memory stick for still photos. I’m also not 100% sure I don’t need a USB
cable to do the webcam functionality, but right now I’m assuming I can
use the firewire cable for both. You cannot use the USB cable to
transfer video with that camera, you’ll need the firewire.&lt;/p&gt;
&lt;p&gt;I passed on ordering a camcorder case. My plan there is to get all the
stuff in house and then go to target and see what would hold all of it
without taking up a ton of space. I did not see a significant savings by
ordering the case on the Internet. I also passed on tripods, lens
filters, etc. I just don’t think I’ll be going that professional, and if
I did, I’d spend a lot more on the camera (the $800 price point will get
you to a semi-pro level). I did get a phone call from the people I
bought the camcorder from trying to sell me all the accessories. I told
them “I had an older handycam and already own all the accessories”. That
got them off my back pretty quickly, but I did spend the extra $30 for a
2-year warranty. I have heard rumors that the touch screen tends to wear
out and it is not covered by Sony’s warranty.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The way it should be</title>
      <link>https://emil.lerch.org/2006-06-02-the-way-it-should-be/</link>
      <pubDate>Fri, 02 Jun 2006 09:42:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-06-02-the-way-it-should-be/</guid>
      <description>&lt;p&gt;Blogging from a &lt;a href=&#34;http://www.google.com/maps?hl=en&amp;amp;lr=&amp;amp;safe=off&amp;amp;q=urban+grind&amp;amp;near=Portland,+OR&amp;amp;radius=0.0&amp;amp;latlng=45523611,-122675000,10901414917128223452&amp;amp;sa=X&amp;amp;oi=local&amp;amp;ct=result&amp;amp;cd=2&#34;&gt;coffee
shop&lt;/a&gt;
in downtown Portland for
&lt;a href=&#34;http://www.personaltelco.net/static/index.html&#34;&gt;free&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Is it really that much to ask for?</title>
      <link>https://emil.lerch.org/2006-05-23-is-it-really-that-much-to-ask-for/</link>
      <pubDate>Tue, 23 May 2006 12:15:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-05-23-is-it-really-that-much-to-ask-for/</guid>
      <description>&lt;p&gt;So I&amp;rsquo;ve been keeping my eyes open for a cheap but somewhat flexible
&lt;a href=&#34;http://en.wikipedia.org/wiki/Network-attached_storage&#34;&gt;NAS&lt;/a&gt; solution.
Quick rundown of my requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stores data&lt;/li&gt;
&lt;li&gt;Allows always ready backup (usually this translates to &lt;a href=&#34;http://en.wikipedia.org/wiki/Redundant_array_of_independent_disks&#34;&gt;RAID 1
mirroring&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Allows connection via
&lt;a href=&#34;http://en.wikipedia.org/wiki/Server_Message_Block&#34;&gt;SMB&lt;/a&gt;, so I don&amp;rsquo;t
have to install software from a hardware manufacturer (this is my
best known method for not getting weird errors)&lt;/li&gt;
&lt;li&gt;Plugs into wired Ethernet&lt;/li&gt;
&lt;li&gt;Expandable&lt;/li&gt;
&lt;li&gt;Free (or at least cheap)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The other day I ran across what appears to be such a device, from a
company called
&lt;a href=&#34;http://www.simpletech.com/commercial/simpleshare/index.php&#34;&gt;SimpleTech&lt;/a&gt;.
Now, what&amp;rsquo;s amazing to me is that there is very little competition for
this. I did manage to find &lt;a href=&#34;http://www.buffalotech.com/products/product-detail.php?productid=131&amp;amp;categoryid=28&#34;&gt;something very close from Buffalo
Technology&lt;/a&gt;,
and it may even be better as it does support streaming media. However,
it&amp;rsquo;s backup strategy is a scheduled backup rather than RAID 1 (which
might be just fine).&lt;/p&gt;
&lt;p&gt;Other products tend to be a couple hundred dollars more expensive and
include a bunch of stuff that is nice, but not necessary to a consumer,
or in my opinion, to a small business (&amp;lt;10 employees, probably
retail, not a lot of discretionary funds). I&amp;rsquo;m going to give it a little
time and then go get the Buffalo or SimpleTech.&lt;br&gt;
&lt;a href=&#34;http://www.buffalotech.com/products/product-detail.php?productid=131&amp;amp;categoryid=28&#34;&gt;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Gas prices</title>
      <link>https://emil.lerch.org/2006-05-22-gas-prices/</link>
      <pubDate>Mon, 22 May 2006 10:24:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-05-22-gas-prices/</guid>
      <description>&lt;p&gt;One of my soapbox items for the press is their failure to report
historical prices/costs on a &lt;a href=&#34;http://en.wikipedia.org/wiki/Real_vs._nominal_in_economics&#34;&gt;real
basis&lt;/a&gt;. This
is a disservice and misleads people. Over the last year there&amp;rsquo;s been a
lot of discussion about oil prices. Today I searched/found a &lt;a href=&#34;http://api-ec.api.org/filelibrary/PumpPriceUpdate.pdf&#34;&gt;good/short
article on gasoline and oil
prices&lt;/a&gt; from the
&lt;a href=&#34;http://www.api.org&#34;&gt;American Petrolium Institute&lt;/a&gt; that reports on a
real basis. The bad news? On a real basis, we are indeed near (but not
quite at) historical record highs for gas at the pump&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The goodness of lots of bandwidth</title>
      <link>https://emil.lerch.org/2006-05-22-the-goodness-of-lots-of-bandwidth/</link>
      <pubDate>Mon, 22 May 2006 10:15:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-05-22-the-goodness-of-lots-of-bandwidth/</guid>
      <description>&lt;p&gt;So FIOS appears to be fairly stable now with the exception of random
router reboots about once/wk. I won&amp;rsquo;t rant on that right now, but I
would like to quickly brag. Last Friday, Kelly and I were working from
home, and for an hour, we:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Both used the phone (her on &lt;a href=&#34;http://www.vonage.com&#34;&gt;Vonage&lt;/a&gt;, me on
&lt;a href=&#34;http://www.skype.com&#34;&gt;Skype&lt;/a&gt; using
&lt;a href=&#34;http://www.skype.com/products/skypeout/&#34;&gt;SkypeOut&lt;/a&gt;, which is free
until the end of the year to US/Canada phone numbers)&lt;/li&gt;
&lt;li&gt;She used VPN and Netmeeting&lt;/li&gt;
&lt;li&gt;I used a form of VPN and ran reports/data extracts during the
meeting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The service worked fine the whole time (with the exception of a random
router reboot&amp;hellip;grr).&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Verizon FIOS Update</title>
      <link>https://emil.lerch.org/2006-05-09-verizon-fios-update/</link>
      <pubDate>Tue, 09 May 2006 16:24:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-05-09-verizon-fios-update/</guid>
      <description>&lt;p&gt;Ugh&amp;hellip;so some mixed results in the really high speed world. Got FIOS
installed cinco de Mayo and all was great - for a day and a half. On
Sunday we started having problems (no access at all), and to make things
worse, the phone number that was given on literally every page of our
welcome guide and used in the &amp;ldquo;Need Help? We&amp;rsquo;re on call for you 24x7&amp;rdquo;
section of our welcome letter got us nowhere. We were both completely
unsuccessful finding the right path to get to 24x7 support, and a
complaint letter has been drafted.&lt;/p&gt;
&lt;p&gt;On Monday (afternoon) I got a chance to call during business hours and
got routed around. I now have the super-secret tech support number of
888-339-7333. I&amp;rsquo;m not sure how this relates, but there is another number
of 888-991-4999 that someone called &amp;ldquo;FIOS Service&amp;rdquo; (compare with &amp;ldquo;FIOS
Tech Support&amp;rdquo; for the other number). They fixed us at the time by doing
something on their end, which I now suspect was that they rebooted the
box on the outside of the house. I asked at the time, but the guy on the
phone didn&amp;rsquo;t know - he was relaying between me and the network people.&lt;/p&gt;
&lt;p&gt;So, all was good&amp;hellip;until this morning at 6AM when I had a meeting with
India. Using &lt;a href=&#34;http://www.vonage.com&#34;&gt;Vonage&lt;/a&gt;, not having broadband is
like, really a downer, and I went back to my Comcast broadband for the
phone call (and for data while working this morning). This time, I had
download speeds of anywhere between 2-5Mbps, but almost no upload (about
60kbps). The speeds kept varying wildly, and over the course of the next
hour or so, they gradually got a little better. Latency was no problem
at all, but I was getting a lot of dropped packets as well. This was
really weird.&lt;/p&gt;
&lt;p&gt;I called Verizon about 8AM and they rebooted the box outside, and it was
better, but still not &amp;ldquo;right&amp;rdquo;. It took about 40 minutes for me to
convince them that it was not a problem with my computer (they remote
connected to two of my machines and ran optimizers and stuff). In their
defense, everything they could see (which was to the box outside the
house) looked ok on their diagnostics. They finally decided it was weird
enough to send someone out. Oddly, as the morning drug on, my speeds
were still intermittent, but they were slowly getting better.&lt;/p&gt;
&lt;p&gt;The technicians (yeah, two of them - pretty cool) got here about 10:30
and they took a look at the box outside as well as one of my machines.
The one technician ran a ping test on the first hop router while the guy
at the box outside my house noticed that the wires in the RJ45 connector
didn&amp;rsquo;t have a firm connection. Bingo - this explains the problems! In
cold weather (overnight, early in the morning) the metal wires
contracted, making the slighly unfirm connection really, really shaky,
to the point where it just wouldn&amp;rsquo;t do anything anymore. Upload speed
was effected because it was one of the TX wires&amp;hellip;pretty wild.&lt;/p&gt;
&lt;p&gt;So they fixed the connector problem, but the speed is still not quite
&amp;ldquo;right&amp;rdquo;. They take a closer look at my setup, and they say, &amp;ldquo;who
installed this? The installer used internal wiring, which is Cat 3, not
Cat 5!&amp;rdquo;. After wiring up a new (Cat 5) outlet the problem is now fixed
and I have some killer broadband.&lt;/p&gt;
&lt;p&gt;As a reward for any west coast people reading this far, the Verizon guys
on the way out the door mentioned to me that on the east coast, what I
pay ($44.95/mo) gets you 20Mbps/5Mbps (vs. my 15Mbps/2Mbps), and that
should be rolled out here as well at some point&amp;hellip;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Connected, or not?</title>
      <link>https://emil.lerch.org/2006-04-29-connected-or-not/</link>
      <pubDate>Sat, 29 Apr 2006 14:31:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-04-29-connected-or-not/</guid>
      <description>&lt;p&gt;I constantly struggle with the concept of connected vs. unconnected
operation. One thing I hate when traveling is that I don&amp;rsquo;t have access
to many common applications (the system I am responsible for at work,
our bug tracking system, many, many random web sites, etc.). However, I
also do not like the idea of installing all kinds of applications on my
notebook and syncronizing all the time.&lt;/p&gt;
&lt;p&gt;For applications, I&amp;rsquo;m starting to come down on the side of building
interfaces based on the client. If your clients are all Windows, it
probably makes sense to go with a &lt;a href=&#34;http://msdn.microsoft.com/msdnmag/issues/04/05/ClickOnce/&#34;&gt;.NET
ClickOnce&lt;/a&gt;
application with &lt;a href=&#34;http://www.microsoft.com/sql/editions/sqlmobile/default.mspx&#34;&gt;SQL
Everywhere&lt;/a&gt;
as a local data store (of course, you get to have fun building in all
the syncronization). If your clients are not all Windows-based, then go
with a web application, and if the costs justify the it, build the click
once app as well for convenience.&lt;/p&gt;
&lt;p&gt;Similarly (and possibly a more complex problem), what to do about email?
Do you send that large attachment out? If you do, your
coworkers/friends/whatever will not be able to edit the document and
easily keep track of changes - you end up merging by hand constantly. If
you don&amp;rsquo;t, your recipients do not have offline access and may have a
difficult time finding the document again. I don&amp;rsquo;t have a good answer
for that, but I suspect that a really good answer would be worth money
to someone&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Got FIOS?</title>
      <link>https://emil.lerch.org/2006-04-29-got-fios/</link>
      <pubDate>Sat, 29 Apr 2006 14:18:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-04-29-got-fios/</guid>
      <description>&lt;p&gt;Well, the trucks have rolled through the neighborhood laying down fiber
optic cable through the streets. While I don&amp;rsquo;t have
&lt;a href=&#34;http://www.verizonfios.com&#34;&gt;FIOS&lt;/a&gt; yet, it will be installed May 5th.
For the uninitiated, the service is high speed brodband Internet access
provided by Fiber Optic cable directly to the house. Instead of a cable
or DSL modem, what gets installed in your house is an ethernet jack. I
like the service for the following reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It&amp;rsquo;s fast (15Mb/sec, which is triple the speed of cable service
here)&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s fast (2Mb/sec upload speed, which is 6 times the speed of cable
service here). Why do I care about upload speed? When working from
home, talking on Vonage (over the Internet) and sharing applications
in Netmeeting becomes painful.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s priced well. For $1 less than my already-discounted broadband
cable service, I get the 15Mb/2Mb package. If I were even cheaper
than I already am, I could have 5Mb/2Mb service for $10 less. That
would give me roughly the same service I have now, but would vastly
improve my upload speed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I may have splurged a bit on the 15Mb package&amp;hellip;not sure I&amp;rsquo;ll use all
the download speed. But guaranteed, it will be nice.&lt;/p&gt;
&lt;p&gt;As an aside, the power of marketing and brand recognition is really
evident in this space. I could pay 3x as much with Comcast (or Verizon)
for the same service I get from Vonage. Verizon FIOS is very new, so
they are priced so well that there&amp;rsquo;s just no reason to switch. Either
you get better service for the same price (like me), or you save
$10/month. With 1 month free and a 30 day money back guarantee, there is
no customer switching costs. It will be interesting to see how Comcast
reacts to the competitive threat when FIOS starts getting mindshare&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Gas prices and irresponsible reporting</title>
      <link>https://emil.lerch.org/2006-04-28-gas-prices-and-irresponsible-reporting/</link>
      <pubDate>Fri, 28 Apr 2006 08:12:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-04-28-gas-prices-and-irresponsible-reporting/</guid>
      <description>&lt;p&gt;There&amp;rsquo;s been much ado about gas prices lately. I&amp;rsquo;ve seen a lot of
irresponsible (or just plain ignorant) reporting on the matter.
Headlines like &lt;a href=&#34;http://news.yahoo.com/s/ap/20060428/ap_on_bi_ge/earns_chevron&#34;&gt;&amp;ldquo;Chevron Earnings Soar 49 Percent to
$4B&amp;rdquo;&lt;/a&gt; and
&lt;a href=&#34;http://biz.yahoo.com/ap/060427/earns_exxon_mobil.html?.v=9&#34;&gt;&amp;ldquo;Exxon Mobil 1Q Profit Up 7 Pct. to
$8.4B&amp;rdquo;&lt;/a&gt;
provides very little context, and if you actually read the report,
you&amp;rsquo;ll notice a lot of dollar figures. I have not seen (or heard on the
news) a single non-financial report of gas industry profit that even
mentions margin. I might be picking on AP a little here, because both my
examples were AP, but they are one of the most read news sources, so
they have a greater responsibility to get things right. Anyway, the
whole thing reminds me of deficit headlines that don&amp;rsquo;t compare deficit
as a percentage of GDP.&lt;/p&gt;
&lt;p&gt;This post is about bad reporting, but I&amp;rsquo;ll mention that my opinion on
the gas thing is that gas is a commodity and that as such, the oil
companies are price takers, not price makers, and so they have very
little control over these things. An &lt;a href=&#34;http://finance.yahoo.com/q/ks?s=CVX&#34;&gt;11%
(Chevron)&lt;/a&gt;/&lt;a href=&#34;http://finance.yahoo.com/q/ks?s=XOM&#34;&gt;16%
(ExxonMobil)&lt;/a&gt; operating margin is
pretty-freaking far from &amp;ldquo;windfall profits&amp;rdquo;, and appears in line with
other commodity industries like
&lt;a href=&#34;http://biz.yahoo.com/p/130conameu.html&#34;&gt;steel&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Smartphone Applications</title>
      <link>https://emil.lerch.org/2006-04-23-smartphone-applications/</link>
      <pubDate>Sun, 23 Apr 2006 11:52:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-04-23-smartphone-applications/</guid>
      <description>&lt;p&gt;Hmm&amp;hellip;so Smartphone 2003 (a.k.a. Windows Mobile 2003 a.k.a. Windows CE
4.21) doesn&amp;rsquo;t come with a version of notepad for short notes.&lt;/p&gt;
&lt;p&gt;Problem now &lt;a href=&#34;http://sourceforge.net/projects/notepadsf&#34;&gt;solved&lt;/a&gt;. I wrote
a quick notepad application. Curiously, Smartphone 2003 doesn&amp;rsquo;t have a
standard control for navigating the file system. The guys at
&lt;a href=&#34;http://www.opennetcf.org/CategoryView.aspx?category=Home&#34;&gt;OpenNetCF&lt;/a&gt;
have some great extensions, and although I can&amp;rsquo;t find it right now,
there is are File Dialogs to help you open and save documents.&lt;/p&gt;
&lt;p&gt;I also decided to write a &lt;a href=&#34;http://sourceforge.net/projects/stopwatchplussf&#34;&gt;stopwatch
application&lt;/a&gt;. The
application has standard start/stop/reset/lap functions, but also adds
logging to a file (no, you can&amp;rsquo;t change it). It also has a unique
feature that helps explain why I built it: It will time contractions
(and display the last hour&amp;rsquo;s average frequency and duration), show you
the last 5 frequency and durations of the contractions, and tell you
when it&amp;rsquo;s time to go to the hospital. I expect it to be quite handy
around the &lt;a href=&#34;http://kellyemil.blogspot.com/&#34;&gt;end of June&amp;hellip;&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Yahoo! Mail Issue Resolved</title>
      <link>https://emil.lerch.org/2006-04-09-yahoo-mail-issue-resolved/</link>
      <pubDate>Sun, 09 Apr 2006 13:15:00 -0700</pubDate>
      
      <guid>https://emil.lerch.org/2006-04-09-yahoo-mail-issue-resolved/</guid>
      <description>&lt;p&gt;As mentioned in a &lt;a href=&#34;http://emillerch.blogspot.com/2006/03/gmail-vs-windows-live-mail-vs-yahoo.html&#34;&gt;previous
post&lt;/a&gt;,
I had some significant issues getting Yahoo! Mail up and running. The
issue is now resolved. For the record, I believe that the issue stemmed
from the fact that I established my Yahoo! ID back in 1995 (or
thereabouts) and did not sign up for mail service on the ID at the time.
I did not really want to establish a new ID for the test. Anyway, here&amp;rsquo;s
the scoop:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;February 25th: Original ticket submitted&lt;/li&gt;
&lt;li&gt;February 27th: Stock answer that the problem is temporary, and maybe
I should click refresh on my browser. Not sure why it took two days
for what an autoresponder could have done, but whatever. Responded
that I tried IE and Firefox, logged out, logged in, etc., etc.&lt;/li&gt;
&lt;li&gt;February 28th: Received response that they are looking into the
issue.&lt;/li&gt;
&lt;li&gt;March 5th: Responded to Yahoo! asking for an update. Received
response that they&amp;rsquo;re still working on it.&lt;/li&gt;
&lt;li&gt;March 22nd: Responded to Yahoo! asking for an update.&lt;/li&gt;
&lt;li&gt;March 29th: After a full week, I received a message saying &amp;ldquo;the
issue may have been transient and appears to be resolved now&amp;rdquo;. My
conclusion at this point - Yahoo! did not actually look into the
issue over the entire month. I&amp;rsquo;m basically back at &amp;ldquo;please hit
refresh&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;April 1st: Hadn&amp;rsquo;t checked into my email, so I read the March 29th
message and responded with &amp;ldquo;This is not yet resolved.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;April 2nd: Finally I seem to get someone&amp;rsquo;s attention. They ask me
for a bunch of account details (not sure why they need this, but
they&amp;rsquo;re finally looking at my account setup rather than assuming I&amp;rsquo;m
reporting a general server outage).&lt;/li&gt;
&lt;li&gt;April 3rd: Respond with account details&lt;/li&gt;
&lt;li&gt;April 3rd: Yahoo! &lt;!-- raw HTML omitted --&gt;replies to my
response&lt;!-- raw HTML omitted --&gt; asking for account details. After a month of back
and forth blaming me for not hitting refresh, now they prove they
can&amp;rsquo;t even read.&lt;/li&gt;
&lt;li&gt;April 4th: I send my reply &amp;ldquo;I already did. Please see original
message below&amp;rdquo;&lt;/li&gt;
&lt;li&gt;April 6th: &lt;!-- raw HTML omitted --&gt;Michelle&lt;!-- raw HTML omitted --&gt; replies
to me stating a definitive, &amp;ldquo;We have completed our investigation of
the issue you reported, and were able to identify the cause and take
corrective mesaures to address it&amp;hellip;&amp;rdquo;. This sounds positive!&lt;/li&gt;
&lt;li&gt;April 7th: I verify that I now have a Yahoo! mail account and can
log in. I send a &amp;ldquo;It&amp;rsquo;s resolved - thank you!&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The entire time I had hoped to actually call them so I could convince
someone on the phone that this is an account issue, not a server issue,
but alas, no support phone numbers were to be found. I actually
considered walking to their &lt;a href=&#34;http://www.google.com/local?f=q&amp;amp;hl=en&amp;amp;q=5300+NE+Elam+Young+Pkwy+Hillsboro,+OR+97124&#34;&gt;offices near Kelly&amp;rsquo;s
work&lt;/a&gt;
either to explain the situation, strangle someone, or both.&lt;/p&gt;
&lt;p&gt;Anyway, in a record 7 weeks, Yahoo! was able to fix my problem. One good
thing in all this is that Yahoo! sent me a survey after nearly every
response. I sent some very strong messages in both the scores and the
write in comments. I hope they read them.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>April Fools Day comes early@Intel</title>
      <link>https://emil.lerch.org/2006-03-30-april-fools-day-comes-early-intel/</link>
      <pubDate>Thu, 30 Mar 2006 14:39:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-03-30-april-fools-day-comes-early-intel/</guid>
      <description>&lt;p&gt;We had a fantastically well done April Fools joke played on the 100k or
so Intel employees today.&lt;/p&gt;
&lt;p&gt;Everyone in the company received an email stating that there was a major
new deal between Apple and Intel, and that executives would host a
webcast to announce it today at 8:30AM Pacific. The webcast has been
replaying every half hour since then. Very little details have been
posted about the content of the announcement, and the article on our
internal web site states that the announcement will be made public to
the stock market tomorrow (Friday March 31st).&lt;/p&gt;
&lt;p&gt;I missed the original webcast (conflicting meeting), but caught it later
in the day. Keep in mind this is an April Fools joke&amp;hellip;but the
executives announced a merger between Intel and Apple. They demonstrated
the new name (mactel), the new logo, new tag line (leap different), and
talked about transition plans, and why they decided to merge. By the
time they got to uniforms (black turtleneck) and Intel, wait, sorry -
mactel values of &amp;ldquo;Be Cool&amp;rdquo;, it was pretty obvious it was a joke.&lt;/p&gt;
&lt;p&gt;Intel does a great job with April fools every year. Normally they turn
our internal website into a huge pile of fake news, interviews, etc.
This year, April 1st lands on a Saturday, so they thought they&amp;rsquo;d get us
all a little early. Since it wasn&amp;rsquo;t the standard fake news that we&amp;rsquo;ve
now become accustomed to, it was extremely effective. We may be a big
company, but we do have fun every once in a while.&lt;/p&gt;
&lt;p&gt;I still hope they do the fake news&amp;hellip;it may even be worth logging into
work on Saturday just to read it. ;-)&lt;/p&gt;
&lt;p&gt;&lt;!-- raw HTML omitted --&gt;Disclaimer: The content of this
message is my personal opinion only and although I am an employee of
Intel, the statements I make here in no way represent Intel&amp;rsquo;s position
on the issue, nor am I authorized to speak on behalf of Intel on this
matter.&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Live Custom Domains vs. GMail Hosted</title>
      <link>https://emil.lerch.org/2006-03-30-live-custom-domains-vs-gmail-hosted/</link>
      <pubDate>Thu, 30 Mar 2006 10:36:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-03-30-live-custom-domains-vs-gmail-hosted/</guid>
      <description>&lt;p&gt;OK, so this is a bit bleeding edge, but very, very cool. Please take
this mini-review with a very large grain of salt because both services
are evolving fast.&lt;/p&gt;
&lt;p&gt;Both &lt;a href=&#34;http://domains.live.com&#34;&gt;Windows Live Custom Domains&lt;/a&gt; and &lt;a href=&#34;https://www.google.com/hosted&#34;&gt;Gmail
For Your Domain&lt;/a&gt; provide the same basic
functionality, which is the ability to use the corresponding company&amp;rsquo;s
mail service (either Live Mail/Hotmail or GMail), to host the users in
your own domain.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been doing something similar to this for my own email at my domain,
by setting up my accounts to forward messages to Windows Live Mail or
Gmail (depending on the account). The problem here is that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;People you&amp;rsquo;re sending mail to can figure out where the mail comes
from (Hotmail/Live Mail uses Reply-To, Gmail sets the From: address
but uses a Sender: address of Gmail that will show up in some mail
clients as &amp;ldquo;on behalf of&amp;rdquo;).&lt;/li&gt;
&lt;li&gt;Only effective for your account - not everyone in the domain.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I credit Windows Live Custom Domains as having the first incarnation of
the service. Basically, you tell the DNS system that the mail server
(technically, this is changing your MX record) is not your hosting
provider&amp;rsquo;s mail server, but rather it&amp;rsquo;s Google&amp;rsquo;s or Microsoft&amp;rsquo;s mail
server. Some issues with any service like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There&amp;rsquo;s no halfway - it&amp;rsquo;s either everyone in the domain or noone&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s hard to change back&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&amp;rsquo;s another issue that both services also have, at least right now,
which is the ability to pull in all your existing mail. Of course, I
believe both Hotmail and Gmail have the ability to pull from POP, so
there is at least that migration path.&lt;/p&gt;
&lt;p&gt;For the tests, I registered a free .info domain (lerchweb.info) at
&lt;a href=&#34;http://www.1and1.com&#34;&gt;1&amp;amp;1&lt;/a&gt;, a large hosting provider. I signed up for
the Google Beta and the Windows Live Custom Domains beta (internally
this is known as BYOD for Bring Your Own Domain). They both work
essentially the same, with the following differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows Live requires that each user establish a Hotmail/Windows
Live account. So far (and this is preliminary), Google does not
require this. I would give Google the edge on this point, but
Windows Live may have a better privacy story to tell.&lt;/li&gt;
&lt;li&gt;Google lets you brand GMail with your own domain logo. Very cool
touch.&lt;/li&gt;
&lt;li&gt;Google lets you import users from a spreadsheet (exported to CSV).
Also very cool, and this is a new feature that Windows Live may
replicate.&lt;/li&gt;
&lt;li&gt;Google beta took me about a month to become a member. Windows Live
Custom Domains was immediate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s hard to ignore my feelings about the actual mail system (I tend to
prefer Google), but judging on simply the domain hosting features, I
think I give this one to Windows Live Custom Domains, primarily because
it takes so long to get into Google&amp;rsquo;s beta.&lt;/p&gt;
&lt;p&gt;Next step is to get another free .info domain so I&amp;rsquo;m not repointing
things all the time so I can do more than a 24 hour evaluation. This
concept excites me, and I look forward to some cutthroat competition!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Slow day</title>
      <link>https://emil.lerch.org/2006-03-30-slow-day/</link>
      <pubDate>Thu, 30 Mar 2006 10:27:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-03-30-slow-day/</guid>
      <description>&lt;p&gt;This is one of those very rare events for me - a slow day. This day has
been brought to me by a a series of events that have all coincided at
once:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;India has a holiday, so development and QA teams are not working&lt;/li&gt;
&lt;li&gt;Most of my projects are in QA right now&lt;/li&gt;
&lt;li&gt;A major event is going to occur next Monday at work, and in order
for me to finish preparing I need to get information from some
coworkers&lt;/li&gt;
&lt;li&gt;All systems are running fairly well&lt;/li&gt;
&lt;li&gt;Projects have been completing, but fewer new projects have been
created over the last 3-4 weeks, so overall workload has been light
the last 2 weeks, allowing me time to catch up&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Weird - I don&amp;rsquo;t know what to do with myself. I&amp;rsquo;m definitely not
complaining&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Gmail vs. Windows Live Mail vs. Yahoo Mail</title>
      <link>https://emil.lerch.org/2006-03-22-gmail-vs-windows-live-mail-vs-yahoo-mail/</link>
      <pubDate>Wed, 22 Mar 2006 20:57:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-03-22-gmail-vs-windows-live-mail-vs-yahoo-mail/</guid>
      <description>&lt;p&gt;So I&amp;rsquo;ve been working in several email systems comparing and contrasting
the features. Here&amp;rsquo;s my take:&lt;/p&gt;
&lt;p&gt;Yahoo! Mail: I have a Yahoo! id, but did not set up an email account
originally. I tried to sign up to test things out for this blog entry,
but I get an error message. I dutifully reported the issue to Yahoo
support (issue # KMM58274977V28957L0KM), but have not had any updates
in the past month. I don&amp;rsquo;t think I should be forced to get a new Yahoo!
ID, so they lose.&lt;/p&gt;
&lt;p&gt;GMail: Technically a beta product, it&amp;rsquo;s pretty open to people. If anyone
wants a GMail ID, let me know. I have most of my lerch.org forwarded
here. I don&amp;rsquo;t use the mail search all that often, but I like how the
filters use the standard search terms. The filters are very flexible,
and it&amp;rsquo;s nice to be able to tag items with multiple tags (basically the
equivalent of putting a message into multiple folders simultaneously).
It&amp;rsquo;s very &amp;ldquo;web-like&amp;rdquo;, and the ecosystem is also nice. GMail notifier,
browser plugins, the ability to see mail on Google.com&amp;rsquo;s personalized
home page and POP access all make the system very accessible. On the
downside, visiting GMail with two browsers simultaneously &lt;!-- raw HTML omitted --&gt;really &lt;!-- raw HTML omitted --&gt;slows the system down.&lt;/p&gt;
&lt;p&gt;Windows Live Mail (aka Kahuna): I&amp;rsquo;m now using &amp;ldquo;M2&amp;rdquo; of the beta product
for all my commercial mail from lerch.org. The idea was originally that
it would be the account that would collect spam, but it turned into a
bit more than that. Anyway, it&amp;rsquo;s basically a trimmed-down version of OWA
(Outlook Web Access), with an Outlook look and feel. If you like that,
you&amp;rsquo;ll like this, and you can get automatic notifications through MS
Messenger. Also, rules that you define have priorities, so if two rules
match the same message, you can define which one should apply.&lt;/p&gt;
&lt;p&gt;While Windows Live Mail is very much like Outlook, which should be
comfortable for a lot of people. It does not have POP access, and it
doesn&amp;rsquo;t have the ability to keep a copy of a message in multiple
folders. Also, it does not support all browsers (connecting with Firefox
downlevels to a Hotmail interface, although as a rapidly evolving beta
product Windows Live Mail may address this issue shortly).&lt;/p&gt;
&lt;p&gt;Right now, I do like both, Live Mail primarily for the very polished
interface, GMail for the ecosystem support and filter behavior. I seem
to &lt;!-- raw HTML omitted --&gt;enjoy&lt;!-- raw HTML omitted --&gt; GMail better, and it&amp;rsquo;s
nice to have POP so I can truly archive my mail if I ever want to move
away from the system. I look forward to both systems moving out of
beta.&lt;/p&gt;
&lt;p&gt;Now, what I find really interesting is Windows Live Domains and the fast
follower GMail BYOD (Bring Your Own Domain) service. More to come on
this&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Why I love STX</title>
      <link>https://emil.lerch.org/2006-03-16-why-i-love-stx/</link>
      <pubDate>Thu, 16 Mar 2006 14:20:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-03-16-why-i-love-stx/</guid>
      <description>&lt;p&gt;Seagate = Hard Drives&lt;br&gt;
Hard Drives&amp;hellip;needed for 1st Gen PVRs (e.g. Tivo, Windows Media Center)
and future (High Def) generations&lt;br&gt;
Hard Drives&amp;hellip;needed for personal music players that have lots of
storage (or who add video)&lt;br&gt;
Hard Drives&amp;hellip;needed for advanced gaming machines&lt;br&gt;
Hard Drives&amp;hellip;needed for all the music and/or videos you&amp;rsquo;re
downloading&lt;br&gt;
Hard Drives&amp;hellip;needed for all those 5MP digital pictures you&amp;rsquo;re taking&lt;/p&gt;
&lt;p&gt;Advanced gaming machines, personal music players, digital photography,
peer-to-peer downloading, PVRs&amp;hellip;all are examples of technologies that
are expanding rapidly and are at various (leading) points of the
&lt;a href=&#34;http://en.wikipedia.org/wiki/Diffusion_of_innovations&#34;&gt;technology adoption
curve&lt;/a&gt;. These
technologies are using more and more data, and the technology is quickly
becoming mainstream.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;http://biz.yahoo.com/ic/813.html&#34;&gt;data storage industry&lt;/a&gt; is
dominated by EMC, but EMC is mostly a business storage deal. Other major
consumer data storage businesses are Western Digital and Maxtor. Seagate
is buying Maxtor, and is cheaper (in terms of P/E) than Western Digital
(in my opinion, for no good reason).&lt;/p&gt;
&lt;p&gt;Bottom line is that this company is doing the right things, positioned
well in an expanding industry, and should enjoy higher than average
shareholder returns for the next several years. Major threats? Western
digital drives prices down and kills the industry, but they can still
lower margins and increase turns in an industry that&amp;rsquo;s expanding so
rapidly. My biggest concern would be
optical/&lt;a href=&#34;http://www.inphase-technologies.com/technology/index.html&#34;&gt;holographic&lt;/a&gt;
technology that provides much higher capacities in the same size form
factor, but I believe we&amp;rsquo;re still several years out from that, giving
Seagate an opportunity to develop the technology themselves or purchase
an emerging player in that market.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Continuous Integration</title>
      <link>https://emil.lerch.org/2006-03-11-continuous-integration/</link>
      <pubDate>Sat, 11 Mar 2006 15:38:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-03-11-continuous-integration/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m a big fan of the concept of &lt;a href=&#34;http://en.wikipedia.org/wiki/Continuous_integration&#34;&gt;continuous
integration&lt;/a&gt;, which
is the concept that code is built and tested as early and as often as
possible. As soon as a change is completed/checked in, the code is built
and tested. Usually this is done automatically (as an aside, I wonder
how this approach can work well with enterprise software). Anyway, I saw
&lt;a href=&#34;http://www.jamesshore.com/Blog/Continuous-Integration-on-a-Dollar-a-Day.html&#34;&gt;this
article&lt;/a&gt;
and I found it pretty amusing. Now I have a good reason to get a rubber
chicken&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Travel tip</title>
      <link>https://emil.lerch.org/2006-03-10-travel-tip/</link>
      <pubDate>Fri, 10 Mar 2006 14:18:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-03-10-travel-tip/</guid>
      <description>&lt;p&gt;Just got back from Taiwan a few hours ago, and I&amp;rsquo;m refining my theory
about flying business class. Here are the two rules that I have now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If flying for business and the flight is &amp;gt;6 hours, your company
should pick up business class (Intel does not, and I think the
policy is ridiculous - the difference in how rested you are at the
end of a long flight between business and economy is crazy)&lt;/li&gt;
&lt;li&gt;If you&amp;rsquo;re going to upgrade yourself due to poor company policy, try
to get upgraded on the way back, rather than on the way out. This
may be a work/life priority thing, but for me I&amp;rsquo;d much rather push
through some business meetings than be exhausted when I get home and
want to do things with friends and family.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, I did not get upgraded (was waitlisted but didn&amp;rsquo;t clear
the list) on the way back home, so I&amp;rsquo;m currently exhausted. Off to bed
for a couple hours&amp;hellip;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Quick bit about Apple and Sony</title>
      <link>https://emil.lerch.org/2006-03-05-quick-bit-about-apple-and-sony/</link>
      <pubDate>Sun, 05 Mar 2006 09:16:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-03-05-quick-bit-about-apple-and-sony/</guid>
      <description>&lt;p&gt;I feel much the same way about Sony&amp;rsquo;s products as I do about
&lt;a href=&#34;http://emillerch.blogspot.com/2006/03/much-ado-about-apple.html&#34;&gt;Apple&lt;/a&gt;.
I will never, for instance, buy any product that takes a memory stick.&lt;/p&gt;
&lt;p&gt;However, there&amp;rsquo;s a big difference in my mind between the two companies.
First, Sony appears to be (slowly) realizing the error of it&amp;rsquo;s
proprietary ways. Second, as an investor, Sony is a huge conglomerate
with revenue sources from many areas. While the second fact actually
slows down adoption of open standards, it does smooth out the revenue
stream.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s quite a bit I could also discuss about
&lt;a href=&#34;http://www.%5Cblu-ray.com&#34;&gt;Blu-Ray&lt;/a&gt; here, and while blu-ray has its
drawbacks, it is (at least in theory) an open standard.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Word to the wise on blogspot.com</title>
      <link>https://emil.lerch.org/2006-03-02-word-to-the-wise-on-blogspot-com/</link>
      <pubDate>Thu, 02 Mar 2006 08:35:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-03-02-word-to-the-wise-on-blogspot-com/</guid>
      <description>&lt;p&gt;Umm&amp;hellip;apparently it&amp;rsquo;s bad news to use the &amp;lt; character flippantly on
Blogger. My last post talked about AAPL P/S being &amp;amp;lt4, and when used in
compose mode, it had the effect of mangling my last paragraph. I&amp;rsquo;ve
actually been generally underwhelmed by Blogger&amp;rsquo;s inability to properly
format without forcing me to use HTML. Users should not have to
necessarily understand and manipulate the underlying technology.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t confuse that statement by thinking it means systems should hide
the underlying technology completely - power users should have access.
My point is that general users shouldn&amp;rsquo;t have paragraphs mangled when
they type in a &amp;lt; sign, and have to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Know what broke&lt;/li&gt;
&lt;li&gt;Go to HTML to fix it&lt;/li&gt;
&lt;li&gt;Know the HTML code for &amp;lt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Much Ado about Apple</title>
      <link>https://emil.lerch.org/2006-03-02-much-ado-about-apple/</link>
      <pubDate>Thu, 02 Mar 2006 08:02:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-03-02-much-ado-about-apple/</guid>
      <description>&lt;p&gt;Working for Intel, I&amp;rsquo;ve been hearing even more than the average person
about Apple and how great it is. There are a lot of great things about
Apple - here&amp;rsquo;s a few:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Great product design: I don&amp;rsquo;t know anyone who doubts or questions
Apple&amp;rsquo;s product design prowess. This involves both hardware (iPod,
iMac) and software (OSX).&lt;/li&gt;
&lt;li&gt;Steve Jobs: Steve definitely has a way about him. He is a great
leader; however, in my mind not necessarily a good manager.&lt;/li&gt;
&lt;li&gt;Recent stock price: A year ago, AAPL was trading just over
$40/share. It made it over $80 before settling back to it&amp;rsquo;s current
price of $69.35 (10:55AM EST March 2nd, 2006).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please, stop the insanity. Apple will always be a player, and never be
&lt;strong&gt;the&lt;/strong&gt; player, at least not for very long, at least not while Steve
Jobs is running the ship. Why? Despite 20 years of evidence to the
contrary, Steve still seems to believe that proprietary systems,
vertical integration and customer lock-in are the way to profitability,
market share and investor value. When they hit on a great product, the
stock will go up. When they can no longer innovate the product to keep
its popularity up, the fad will go away, and Apple will lose revenues,
profit, market share, and stock value. I see no evidence to suggest a
sustainable model.&lt;/p&gt;
&lt;p&gt;Sometimes I look at a stock and am completely at a loss to explain how
the market can value the company the way it does. In Apple&amp;rsquo;s case, P/S
right now is &amp;amp;lt4, which does a fair job of explaining it, despite a
37.48 ttm P/E and a 26.73 forward P/E. However, Apple&amp;rsquo;s company and
stock, in my opinion, is a house of cards, and I&amp;rsquo;ll stay away from AAPL,
despite a belief that in the short run, there are profits to be had. Oh,
and my music player? It&amp;rsquo;s a &lt;a href=&#34;http://www.amazon.com/gp/product/B000779T4Y/104-3040708-0690318?v=glance&amp;amp;n=172282&#34;&gt;Creative MuVo TX FM
(1GB)&lt;/a&gt;.
I&amp;rsquo;ll write about the virtues of it in another post.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Wow - last post in 2004</title>
      <link>https://emil.lerch.org/2006-03-01-wow-last-post-in-2004/</link>
      <pubDate>Wed, 01 Mar 2006 10:45:00 -0800</pubDate>
      
      <guid>https://emil.lerch.org/2006-03-01-wow-last-post-in-2004/</guid>
      <description>&lt;p&gt;So, with energies poured into other things, it&amp;rsquo;s been a very, very long
time. My intention from here out is to be a little more regular (I&amp;rsquo;m
shooting for at least once/yr). ;-)&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll come back over the next couple days to talk about the baby, ASP.Net
2.0, the new car, phone/mp3 player, etc., etc. Hopefully with the baby
site I won&amp;rsquo;t be posting to a crowd of no one!&lt;/p&gt;
&lt;p&gt;With the baby I have finished up doing an application for our
&lt;a href=&#34;http://www.lerch.org/baby&#34;&gt;contest&lt;/a&gt;. Couple factoids about the
site/technology/industry:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.NET hosting providers are consistently more expensive, especially
if you want a database. I&amp;rsquo;m sure this is due to an MS tax, but also
makes me wonder about TCO for .NET.&lt;/li&gt;
&lt;li&gt;ASP.Net 2.0, which really does do a good job of killing off most of
the hand-coding that used to be needed. Typical Microsoft evolution
means that figuring out/controlling what it&amp;rsquo;s doing is more
difficult if you don&amp;rsquo;t want the packaged behavior.&lt;/li&gt;
&lt;li&gt;My
&lt;a href=&#34;http://reviews.cnet.com/Audiovox_SMT5600/4505-6452_7-31138516.html&#34;&gt;phone&lt;/a&gt;
has a web browser, which I will need when the baby is born and I
need to call people. One of the most difficult/time consuming parts
of the site was getting the infrastructure to understand that my
phone can accept cookies. Not absolutely necessary, but I did not
want to have to log in (which is necessary for the admin interface)
during all the excitement.&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
