<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/default.xsl"?>
<fr:tree xmlns:fr="http://www.forester-notes.org" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:xml="http://www.w3.org/XML/1998/namespace" root="false" base-url="/">
  <fr:frontmatter>
    <fr:authors>
      <fr:author>
        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
      </fr:author>
    </fr:authors>
    <fr:date>
      <fr:year>2026</fr:year>
      <fr:month>9</fr:month>
      <fr:day>12</fr:day>
    </fr:date>
    <fr:uri>https://patrick.sirref.org/weekly-2026-w37/</fr:uri>
    <fr:display-uri>weekly-2026-w37</fr:display-uri>
    <fr:route>/weekly-2026-w37/</fr:route>
    <fr:title text="Analysing some Dockerfiles">Analysing some Dockerfiles</fr:title>
  </fr:frontmatter>
  <fr:mainmatter>
    <html:p>After spending a few weeks thinking about the semantics of POSIX shells, this week saw me analysing lots of Dockerfiles looking for some evidence for how they are being used. It also saw environment variables landing in <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> thanks to <fr:link href="/talex5/" title="Thomas Leonard" uri="https://patrick.sirref.org/talex5/" display-uri="talex5" type="local">Thomas</fr:link> and <fr:link href="/anilmadhavapeddy/" title="Anil Madhavapeddy" uri="https://patrick.sirref.org/anilmadhavapeddy/" display-uri="anilmadhavapeddy" type="local">Anil</fr:link>. We also had some <fr:link href="https://github.com/ocaml-ppx/ppxlib/pull/650" type="external">issues in ppxlib migrations</fr:link>.</html:p>
    <fr:tree show-metadata="false" numbered="false">
      <fr:frontmatter>
        <fr:authors>
          <fr:author>
            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
          </fr:author>
        </fr:authors>
        <fr:date>
          <fr:year>2026</fr:year>
          <fr:month>9</fr:month>
          <fr:day>12</fr:day>
        </fr:date>
        <fr:title text="Dockerfile Analysis">Dockerfile Analysis</fr:title>
      </fr:frontmatter>
      <fr:mainmatter>
        <html:p>Part of my research at the moment is thinking about <html:em>how</html:em> shells are used. What commands are run? What constructs are used (e.g. redirections, compound commands...)? <fr:link href="https://docs.docker.com/reference/dockerfile/" type="external">Dockerfiles</fr:link> are a defacto standard means for building container images and for the most part they are a shell with weird semantics and some meta-commands (<fr:link href="/shelter/" title="Shelter" uri="https://patrick.sirref.org/shelter/" display-uri="shelter" type="local">sounds familiar</fr:link>!).</html:p>
        <html:p>I had some assumptions about the kinds of shell commands people use (myself included) when writing Dockerfiles. For example, making use of <html:code>AND</html:code>-lists for grouping commands in <html:code>RUN</html:code> instructions. But I had no evidence to back this up. So I ran some analysis over <fr:link href="/dockerfile2020henkel/" title="Learning from, Understanding, and Supporting DevOps Artifacts for Docker" uri="https://patrick.sirref.org/dockerfile2020henkel/" display-uri="dockerfile2020henkel" type="local">the dataset from this paper from Henkel et al.</fr:link>.</html:p>
        <html:pre><![CDATA[Total Dockerfiles: 218_253 (errors: 808)
Total Docker Instructions: 2_729_118
Total RUN instructions: 657_909 (errors: 4_847)
========

Count (Windows Dockerfiles): 841 / 218_253
Count (ENV commands): 268_898 / 2_729_118
Count (COPY commands): 165_968 / 2_729_118
Count (ADD commands): 86_552 / 2_729_118
Count (WORKDIR commands): 104_112 / 2_729_118
Count (using at least one &&): 267_113 / 657_909
Count (uses of exec): 25 / 657_909
Count (system pkg manager: [apt, apt-get, dnf, apk, pacman, nix, brew, yum]): 192_092 / 657_909
Count (internet retrievers: [curl, wget]): 95_433 / 657_909
Count (might use python): 9_082 / 657_909
Counts:
[(rm, 169_386), (echo, 113_768), (mkdir, 100_924), (cd, 74_904),
 (chmod, 55_998), (set, 25_738), (mv, 24_034), (sed, 21_232),
 (chown, 18_610), (grep, 16_604), (cp, 15_637), (export, 13_154),
 ([, 10_411), (touch, 3_152), (exit, 2_288), (printf, 2_275), (awk, 2_010),
 (., 1_077), (test, 1_004), (source, 904), (pwd, 199), (unset, 105),
 (diff, 40), (trap, 2)]]]></html:pre>
        <html:p>I think there's some evidence here to support some assumptions I already had:</html:p>
        <html:ol>
          <html:li>
            <html:p>A significant proportion of <html:code>RUN</html:code> instructions in Dockerfiles invoke package managers.</html:p>
          </html:li>
          <html:li>
            <html:p>A significant proportion of <html:code>RUN</html:code> instructions invoke <html:code>curl</html:code> or <html:code>wget</html:code>.</html:p>
          </html:li>
          <html:li>
            <html:p>There is something rather <fr:link href="https://en.wikipedia.org/wiki/Zipf's_law" type="external">Zipfian</fr:link> about how shell built-in utilities are used.</html:p>
          </html:li>
          <html:li>
            <html:p>There is limited used of <html:code>exec</html:code> (and therefore limited use of opening file descriptors directly via the shell, as opposed to a command running in a subprocess).</html:p>
          </html:li>
          <html:li>
            <html:p>A lot of time is spent changing directories, either temporally (via <html:code>cd</html:code> in a <html:code>RUN</html:code> instruction, or by using <html:code>WORKDIR</html:code>).</html:p>
          </html:li>
          <html:li>
            <html:p>Lots of <html:code>ENV</html:code> setting.</html:p>
          </html:li>
        </html:ol>
        <fr:tree show-metadata="false" numbered="false">
          <fr:frontmatter>
            <fr:authors>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
              </fr:author>
            </fr:authors>
            <fr:date>
              <fr:year>2026</fr:year>
              <fr:month>9</fr:month>
              <fr:day>12</fr:day>
            </fr:date>
            <fr:title text="Parsing Dockerfiles">Parsing Dockerfiles</fr:title>
          </fr:frontmatter>
          <fr:mainmatter>
            <html:p>For reasons that are probably quite clear, whenever I end up doing a small task like this, I always reach for OCaml to do it. The idea is that I'm invested in that community and hope to see it thrive. And if that means contributing some fixes, libraries or advice along the way, it seems to me that that is time well spent.</html:p>
            <html:p>When I started, I assumed <fr:link href="https://ocaml.org/p/dockerfile/latest" type="external">ocaml-dockerfile</fr:link> had support for parsing files; it turns out it doesn't, but I found <fr:link href="/samoht/" title="Thomas Gazagnaire" uri="https://patrick.sirref.org/samoht/" display-uri="samoht" type="local">Thomas G.</fr:link>'s <fr:link href="https://tangled.org/gazagnaire.org/ocaml-dockerfile/" type="external">port</fr:link> that does. So I took it for a spin and the results were less than I had hoped for. Of the 219,061 Dockerfiles, 97,123 were not parseable! <fr:link href="https://tangled.org/gazagnaire.org/ocaml-dockerfile/pulls/1/round/0" type="external">After a series of commits</fr:link> I managed to get that number down to <html:code>808</html:code>.</html:p>
          </fr:mainmatter>
        </fr:tree>
      </fr:mainmatter>
    </fr:tree>
    <fr:tree show-metadata="false" numbered="false">
      <fr:frontmatter>
        <fr:authors>
          <fr:author>
            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
          </fr:author>
        </fr:authors>
        <fr:date>
          <fr:year>2026</fr:year>
          <fr:month>9</fr:month>
          <fr:day>12</fr:day>
        </fr:date>
        <fr:title text="Eio process environment">Eio process environment</fr:title>
      </fr:frontmatter>
      <fr:mainmatter>
        <html:p>What started out seemingly straightforward (add a capability-based API to <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> for accessing environment variables) ended up being a more nuanced discussion about what the best API would be. In the end, <fr:link href="/talex5/" title="Thomas Leonard" uri="https://patrick.sirref.org/talex5/" display-uri="talex5" type="local">Thomas</fr:link> added <fr:link href="https://github.com/ocaml-multicore/eio/pull/930" type="external">an <html:code>Eio.Process.Env</html:code> API</fr:link> to manipulate soon-to-be opaque environments. This paved the way for the <fr:link href="https://github.com/ocaml-multicore/eio/pull/923" type="external">portable environment support</fr:link>.</html:p>
        <html:p>The API <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> exposes is readonly so we do not have to worry too much about race conditions (even though users are free to <html:code>Unix.putenv</html:code>).</html:p>
        <html:p>One quirk of this work was reaffirming something I had already stumbled across in <fr:link href="/merry-benchmarks/" title="Shell Benchmarks, Back to Writing › Benchmarking Merry " uri="https://patrick.sirref.org/merry-benchmarks/" display-uri="merry-benchmarks" type="local">Merry</fr:link>. Accessing and searching large environments has a cost! For my rather large Nix environment, we can see the slowdown pretty clearly.</html:p>
        <html:pre><![CDATA[                  name,   major-allocated,   minor-allocated,   monotonic-clock
        get_env/all 10,        346.619865,      38577.227215,      29248.828738
        get_env/all 50,       1677.003302,     181788.755923,     138307.102538
       get_env/all 100,       3323.770915,     361218.802888,     274768.577259
   get_env/all_once 10,        121.955078,      14694.900546,      11652.096583
   get_env/all_once 50,        563.133065,      61951.683658,      48161.605588
  get_env/all_once 100,       1113.238600,     121431.915770,      93960.646400
     get_env/direct 10,          0.000000,          0.000000,        974.649587
     get_env/direct 50,          0.000000,          0.000000,       4369.486360
    get_env/direct 100,          0.000000,          0.000000,       8753.393503]]></html:pre>
        <html:p>The <html:code>all</html:code> benchmark calls <html:code>environment</html:code> every time and then finds a variable using <html:code>Eio.Process.Env.get_opt</html:code>, <html:code>all_once</html:code> calls <html:code>environment</html:code> once and then <html:code>Eio.Process.Env.get_opt</html:code> every time and <html:code>direct</html:code> uses the <html:code>Eio.Process.get_opt</html:code> directly every time. The differences are a lot less stark when the environment is pretty small.</html:p>
      </fr:mainmatter>
    </fr:tree>
    <fr:tree show-metadata="false" numbered="false">
      <fr:frontmatter>
        <fr:authors>
          <fr:author>
            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
          </fr:author>
        </fr:authors>
        <fr:date>
          <fr:year>2026</fr:year>
          <fr:month>9</fr:month>
          <fr:day>12</fr:day>
        </fr:date>
        <fr:title text="Ppxlib Migrations">Ppxlib Migrations</fr:title>
      </fr:frontmatter>
      <fr:mainmatter>
        <html:p>Thankfully, <fr:link href="/ppxlib/" title="Ppxlib" uri="https://patrick.sirref.org/ppxlib/" display-uri="ppxlib" type="local">ppxlib</fr:link> has been mostly stable for a number of months. In part, thanks to the diligent efforts of <fr:link href="/nathanreb/" title="Nathan Rebours" uri="https://patrick.sirref.org/nathanreb/" display-uri="nathanreb" type="local">Nathan</fr:link>. However, <fr:link href="/dra27/" title="David Allsopp" uri="https://patrick.sirref.org/dra27/" display-uri="dra27" type="local">David</fr:link> uncovered some unpleasant bugs in the migration code for some new 5.4 AST features. Unfortunately, it is only for attributes which are a source of pain for <fr:link href="/ppxlib/" title="Ppxlib" uri="https://patrick.sirref.org/ppxlib/" display-uri="ppxlib" type="local">ppxlib</fr:link>. I spent some time reviewing the fixes, and it looks like we still need some more work to ensure the attributes attached to package types roundtrip cleanly.</html:p>
      </fr:mainmatter>
    </fr:tree>
    <fr:tree show-metadata="false" numbered="false">
      <fr:frontmatter>
        <fr:authors>
          <fr:author>
            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
          </fr:author>
        </fr:authors>
        <fr:date>
          <fr:year>2026</fr:year>
          <fr:month>9</fr:month>
          <fr:day>12</fr:day>
        </fr:date>
        <fr:title text="Misc.">Misc.</fr:title>
      </fr:frontmatter>
      <fr:mainmatter>
        <html:p>Some other work I recorded separately and include here for the eager reader.</html:p>
        <fr:tree show-metadata="false" numbered="false">
          <fr:frontmatter>
            <fr:authors>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
              </fr:author>
            </fr:authors>
            <fr:date>
              <fr:year>2026</fr:year>
              <fr:month>7</fr:month>
              <fr:day>12</fr:day>
            </fr:date>
            <fr:uri>https://patrick.sirref.org/ocaml-roundup-august-2026/</fr:uri>
            <fr:display-uri>ocaml-roundup-august-2026</fr:display-uri>
            <fr:route>/ocaml-roundup-august-2026/</fr:route>
            <fr:title text="OCaml Roundup: August 2026">OCaml Roundup: August 2026</fr:title>
          </fr:frontmatter>
          <fr:mainmatter>
            <html:p>Much of August was spent thinking about POSIX shell semantics, and in particular extending <fr:link href="/greenberg2020smoosh/" title="Executable formal semantics for the POSIX shell" uri="https://patrick.sirref.org/greenberg2020smoosh/" display-uri="greenberg2020smoosh" type="local">notions from smoosh</fr:link> to include side effects (and maybe concurrency). This is still nascent work, but some ideas and flavours are described below.</html:p>
            <fr:tree show-metadata="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>7</fr:month>
                  <fr:day>12</fr:day>
                </fr:date>
                <fr:title text="Merging Shells">Merging Shells</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>The work came from wanting to merge two shell sessions into one. Setting aside the practicalities of an implementation, it makes sense to start first theoretically. When can I merge two shell executions, and when can I not?</html:p>
                <html:div style="text-align: center;">
<html:img height="400px" src="/bafkrmibyvel7ut7rkmhcagizkntkh2hz5urf7rzif7py5zh7a4nntrv5u4.png" />
</html:div>
                <html:p>It would be nice to think of <html:code>unset DOWNLOAD_DIR</html:code> as a function. This function takes the shell state and the operating state as arguments and returns a modified shell state and an "unmodified" operating system state (at least for our purposes). Meanwhile, <html:code>branch-b</html:code> may have a lot more side effects, but when focusing on the environment only we can see in a sense the two branches are conflict free. Well, sorta. We also need to ensure we do not also read <html:code>DOWNLOAD_DIR</html:code> in <html:code>branch-b</html:code> if we intend to remove it in their merge.</html:p>
                <html:div style="text-align: center;">
<html:img height="300px" src="/bafkrmibympwhn47ko6ogugzj7kcqo7uqoo2xzjqrfxmcems27umlkfigeq.png" />
</html:div>
                <html:p>This work, in part, is borrowing ideas from <fr:link href="/kcrsk-mrdts-2022/" title="Certified mergeable replicated data types" uri="https://patrick.sirref.org/kcrsk-mrdts-2022/" display-uri="kcrsk-mrdts-2022" type="local">MRDTs</fr:link> and can reuse <fr:link href="/void-process/" title="Void Process" uri="https://patrick.sirref.org/void-process/" display-uri="void-process" type="local">void processes</fr:link> to help reason about when side effects can occur, as well as <fr:link href="/open-trace/" title="Opentrace" uri="https://patrick.sirref.org/open-trace/" display-uri="open-trace" type="local">open-trace</fr:link> (which is now becoming <html:code>os-trace</html:code>).</html:p>
                <html:p>I hope to continue this work, see my <fr:link href="/weekly-2026-w37/" title="Analysing some Dockerfiles" uri="https://patrick.sirref.org/weekly-2026-w37/" display-uri="weekly-2026-w37" type="local">latest weekly</fr:link> for more on this.</html:p>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>7</fr:month>
                  <fr:day>12</fr:day>
                </fr:date>
                <fr:title text="Eio in the browser">Eio in the browser</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>I spent some time carving out <html:code>Eio_browser</html:code> into a standalone library, something I depend on for <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> to work <fr:link href="https://patrick.sirref.org/msh" type="external">in the browser</fr:link>.</html:p>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>9</fr:month>
                      <fr:day>12</fr:day>
                    </fr:date>
                    <fr:uri>https://patrick.sirref.org/eio_browser/</fr:uri>
                    <fr:display-uri>eio_browser</fr:display-uri>
                    <fr:route>/eio_browser/</fr:route>
                    <fr:title text="Eio_browser">Eio_browser</fr:title>
                    <fr:meta name="external">https://git.sirref.org/eio_browser</fr:meta>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p><html:code>Eio_browser</html:code> is a backend for <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> that works in the browser. It <fr:link href="https://github.com/ocaml-multicore/eio_js" type="external">reuses work I started</fr:link> at <fr:link href="https://patrick.sirref.org/tarides/" type="external">Tarides</fr:link> that picked up some momentum but has since been a little dormant.</html:p>
                    <html:p>Crucially, it provides a functional standard environment like <html:code>Eio_main</html:code> or any of the OS-specific backends.</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="ocaml-keyword-other">module</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Stdenv</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">sig</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword">type</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">base</html:span>
                        <html:span class="ocaml-source"> =
</html:span>
                        <html:span class="ocaml-source">    &lt; stdout : Eio_unix.sink_ty r
</html:span>
                        <html:span class="ocaml-source">    ; stderr : Eio_unix.sink_ty r
</html:span>
                        <html:span class="ocaml-source">    ; clock : float Eio.Time.clock_ty r
</html:span>
                        <html:span class="ocaml-source">    ; mono_clock : Eio.Time.Mono.ty r
</html:span>
                        <html:span class="ocaml-source">    ; secure_random : Eio.Flow.source_ty r
</html:span>
                        <html:span class="ocaml-source">    ; debug : Eio.Debug.t
</html:span>
                        <html:span class="ocaml-source">    ; backend_id : string &gt;
</html:span>
                        <html:span class="ocaml-keyword-other">end</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-keyword-other">val</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">run</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Stdenv</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">base</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-support-type">unit</html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-support-type">unit</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-comment-doc"><![CDATA[(**]]></html:span>
                        <html:span class="ocaml-comment-doc"><![CDATA[ [run fn] runs [fn] passing it the {! Stdenv.base} environment.]]></html:span>
                        <html:span class="ocaml-comment-doc">
</html:span>
                        <html:span class="ocaml-comment-doc"><![CDATA[    See {! Backend.run} for more information about how effects are handled.]]></html:span>
                        <html:span class="ocaml-comment-doc"><![CDATA[*)]]></html:span>
                        <html:span class="ocaml-source">
</html:span>
                      </html:code>
                    </html:pre>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
          </fr:mainmatter>
        </fr:tree>
      </fr:mainmatter>
    </fr:tree>
  </fr:mainmatter>
  <fr:backmatter>
    <fr:tree show-metadata="false" hidden-when-empty="true">
      <fr:frontmatter>
        <fr:authors />
        <fr:title text="References">References</fr:title>
      </fr:frontmatter>
      <fr:mainmatter>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Vimala Soundarapandian/" type="external">Vimala Soundarapandian</fr:link>
              </fr:author>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Adharsh Kamath/" type="external">Adharsh Kamath</fr:link>
              </fr:author>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Kartik Nagar/" type="external">Kartik Nagar</fr:link>
              </fr:author>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/KC Sivaramakrishnan/" type="external">KC Sivaramakrishnan</fr:link>
              </fr:author>
            </fr:authors>
            <fr:date>
              <fr:year>2022</fr:year>
            </fr:date>
            <fr:uri>https://patrick.sirref.org/kcrsk-mrdts-2022/</fr:uri>
            <fr:display-uri>kcrsk-mrdts-2022</fr:display-uri>
            <fr:route>/kcrsk-mrdts-2022/</fr:route>
            <fr:title text="Certified mergeable replicated data types">Certified mergeable replicated data types</fr:title>
            <fr:taxon>Reference</fr:taxon>
            <fr:meta name="external">https://doi.org/10.1145/3519939.3523735</fr:meta>
            <fr:meta name="journal">Proceedings of the 43rd ACM SIGPLAN International Conference on Programming Language Design and Implementation</fr:meta>
            <fr:meta name="doi">10.1145/3519939.3523735</fr:meta>
          </fr:frontmatter>
          <fr:mainmatter>
            <fr:tree show-metadata="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Vimala Soundarapandian/" type="external">Vimala Soundarapandian</fr:link>
                  </fr:author>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Adharsh Kamath/" type="external">Adharsh Kamath</fr:link>
                  </fr:author>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Kartik Nagar/" type="external">Kartik Nagar</fr:link>
                  </fr:author>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/KC Sivaramakrishnan/" type="external">KC Sivaramakrishnan</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2022</fr:year>
                </fr:date>
                <fr:title text="Abstract">Abstract</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>Replicated data types (RDTs) are data structures that permit concurrent modification of multiple, potentially geo-distributed, replicas without coordination between them. RDTs are designed in such a way that conflicting operations are eventually deterministically reconciled ensuring convergence. Constructing correct RDTs remains a difficult endeavour due to the complexity of reasoning about independently evolving states of the replicas. With the focus on the correctness of RDTs (and rightly so), existing approaches to RDTs are less efficient compared to their sequential counterparts in terms of the time and space complexity of local operations. This is unfortunate since RDTs are often used in a local-first setting where the local operations far outweigh remote communication. This paper presents PEEPUL, a pragmatic approach to building and verifying efficient RDTs. To make reasoning about correctness easier, we cast RDTs in the mould of the distributed version control system, and equip it with a three-way merge function for reconciling conflicting versions. Further, we go beyond just verifying convergence, and provide a methodology to verify arbitrarily complex specifications. We develop a replication-aware simulation relation to relate RDT specifications to their efficient purely functional implementations. We implement PEEPUL as an F* library that discharges proof obligations to an SMT solver. The verified efficient RDTs are extracted as OCaml code and used in Irmin, a distributed database built on the principles of Git.</html:p>
              </fr:mainmatter>
            </fr:tree>
          </fr:mainmatter>
        </fr:tree>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Jordan Henkel/" type="external">Jordan Henkel</fr:link>
              </fr:author>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Christian Bird/" type="external">Christian Bird</fr:link>
              </fr:author>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Shuvendu K. Lahiri" type="external">Shuvendu K. Lahiri</fr:link>
              </fr:author>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Thomas Reps/" type="external">Thomas Reps</fr:link>
              </fr:author>
            </fr:authors>
            <fr:date>
              <fr:year>2020</fr:year>
            </fr:date>
            <fr:uri>https://patrick.sirref.org/dockerfile2020henkel/</fr:uri>
            <fr:display-uri>dockerfile2020henkel</fr:display-uri>
            <fr:route>/dockerfile2020henkel/</fr:route>
            <fr:title text="Learning from, Understanding, and Supporting DevOps Artifacts for Docker">Learning from, Understanding, and Supporting DevOps Artifacts for Docker</fr:title>
            <fr:taxon>Reference</fr:taxon>
            <fr:meta name="doi">10.1145/3377811.3380406</fr:meta>
          </fr:frontmatter>
          <fr:mainmatter>
            <fr:tree show-metadata="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Jordan Henkel/" type="external">Jordan Henkel</fr:link>
                  </fr:author>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Christian Bird/" type="external">Christian Bird</fr:link>
                  </fr:author>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Shuvendu K. Lahiri" type="external">Shuvendu K. Lahiri</fr:link>
                  </fr:author>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Thomas Reps/" type="external">Thomas Reps</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2020</fr:year>
                </fr:date>
                <fr:title text="Abstract">Abstract</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p><![CDATA[			With the growing use of DevOps tools and frameworks, there is an
			increased need for tools and techniques that support more than
			code. The current state-of-the-art in static developer assistance
			for tools like Docker is limited to shallow syntactic validation.
			We identify three core challenges in the realm of learning from,
			understanding, and supporting developers writing DevOps artifacts:
			(i) nested languages in DevOps artifacts, (ii) rule mining, and
			(iii) the lack of semantic rule-based analysis. To address these
			challenges we introduce a toolset, binnacle, that enabled us to
			ingest 900,000 GitHub repositories. Focusing on Docker, we
			extracted approximately 178,000 unique Dockerfiles, and also
			identified a Gold Set of Dockerfiles written by Docker experts. We
			addressed challenge (i) by reducing the number of effectively
			uninterpretable nodes in our ASTs by over 80% via a technique we
			call phased parsing. To address challenge (ii), we introduced a
			novel rule-mining technique capable of recovering two-thirds of the
			rules in a benchmark we curated. Through this automated mining, we
			were able to recover 16 new rules that were not found during manual
			rule collection. To address challenge (iii), we manually collected
			a set of rules for Dockerfiles from commits to the files in the
			Gold Set. These rules encapsulate best practices, avoid docker
			build failures, and improve image size and build latency. We
			created an analyzer that used these rules, and found that, on
			average, Dockerfiles on GitHub violated the rules five times more
			frequently than the Dockerfiles in our Gold Set. We also found that
			industrial Dockerfiles fared no better than those sourced from
			GitHub. The learned rules and analyzer in binnacle can be used to
			aid developers in the IDE when creating Dockerfiles, and in a
			post-hoc fashion to identify issues in, and to improve, existing
			Dockerfiles.]]></html:p>
              </fr:mainmatter>
            </fr:tree>
          </fr:mainmatter>
        </fr:tree>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Michael Greenberg/" type="external">Michael Greenberg</fr:link>
              </fr:author>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Austin J. Blatt" type="external">Austin J. Blatt</fr:link>
              </fr:author>
            </fr:authors>
            <fr:date>
              <fr:year>2019</fr:year>
            </fr:date>
            <fr:uri>https://patrick.sirref.org/greenberg2020smoosh/</fr:uri>
            <fr:display-uri>greenberg2020smoosh</fr:display-uri>
            <fr:route>/greenberg2020smoosh/</fr:route>
            <fr:title text="Executable formal semantics for the POSIX shell">Executable formal semantics for the POSIX shell</fr:title>
            <fr:taxon>Reference</fr:taxon>
            <fr:meta name="external">https://doi.org/10.1145/3371111</fr:meta>
            <fr:meta name="journal">Proc. ACM Program. Lang.</fr:meta>
            <fr:meta name="doi">10.1145/3371111</fr:meta>
          </fr:frontmatter>
          <fr:mainmatter>
            <fr:tree show-metadata="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Michael Greenberg/" type="external">Michael Greenberg</fr:link>
                  </fr:author>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Austin J. Blatt" type="external">Austin J. Blatt</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2019</fr:year>
                </fr:date>
                <fr:title text="Abstract">Abstract</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>The POSIX shell is a widely deployed, powerful tool for managing computer systems. The shell is the expert’s control panel, a necessary tool for configuring, compiling, installing, maintaining, and deploying systems. Even though it is powerful, critical infrastructure, the POSIX shell is maligned and misunderstood. Its power and its subtlety are a dangerous combination. We define a formal, mechanized, executable small-step semantics for the POSIX shell, which we call Smoosh. We compared Smoosh against seven other shells that aim for some measure of POSIX compliance (bash, dash, zsh, OSH, mksh, ksh93, and yash). Using three test suites—the POSIX test suite, the Modernish test suite and shell diagnostic, and a test suite of our own device—we found Smoosh’s semantics to be the most conformant to the POSIX standard. Modernish judges Smoosh to have the fewest bugs (just one, from using dash’s parser) and no quirks. To show that our semantics is useful beyond yielding a conformant, executable shell, we also implemented a symbolic stepper to illuminate the subtle behavior of the shell. Smoosh will serve as a foundation for formal study of the POSIX shell, supporting research on and development of new shells, new tooling for shells, and new shell designs.</html:p>
              </fr:mainmatter>
            </fr:tree>
          </fr:mainmatter>
        </fr:tree>
      </fr:mainmatter>
    </fr:tree>
    <fr:tree show-metadata="false" hidden-when-empty="true">
      <fr:frontmatter>
        <fr:authors />
        <fr:title text="Context">Context</fr:title>
      </fr:frontmatter>
      <fr:mainmatter>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors />
            <fr:uri>https://patrick.sirref.org/weeklies-2026/</fr:uri>
            <fr:display-uri>weeklies-2026</fr:display-uri>
            <fr:route>/weeklies-2026/</fr:route>
            <fr:title text="Patrick Ferris' Weeklies › 2026 "><fr:link href="/weeklies/" title="Patrick Ferris' Weeklies" uri="https://patrick.sirref.org/weeklies/" display-uri="weeklies" type="local">Patrick Ferris' Weeklies</fr:link> › 2026 </fr:title>
          </fr:frontmatter>
          <fr:mainmatter>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>9</fr:month>
                  <fr:day>12</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w37/</fr:uri>
                <fr:display-uri>weekly-2026-w37</fr:display-uri>
                <fr:route>/weekly-2026-w37/</fr:route>
                <fr:title text="Analysing some Dockerfiles">Analysing some Dockerfiles</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>After spending a few weeks thinking about the semantics of POSIX shells, this week saw me analysing lots of Dockerfiles looking for some evidence for how they are being used. It also saw environment variables landing in <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> thanks to <fr:link href="/talex5/" title="Thomas Leonard" uri="https://patrick.sirref.org/talex5/" display-uri="talex5" type="local">Thomas</fr:link> and <fr:link href="/anilmadhavapeddy/" title="Anil Madhavapeddy" uri="https://patrick.sirref.org/anilmadhavapeddy/" display-uri="anilmadhavapeddy" type="local">Anil</fr:link>. We also had some <fr:link href="https://github.com/ocaml-ppx/ppxlib/pull/650" type="external">issues in ppxlib migrations</fr:link>.</html:p>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>9</fr:month>
                      <fr:day>12</fr:day>
                    </fr:date>
                    <fr:title text="Dockerfile Analysis">Dockerfile Analysis</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>Part of my research at the moment is thinking about <html:em>how</html:em> shells are used. What commands are run? What constructs are used (e.g. redirections, compound commands...)? <fr:link href="https://docs.docker.com/reference/dockerfile/" type="external">Dockerfiles</fr:link> are a defacto standard means for building container images and for the most part they are a shell with weird semantics and some meta-commands (<fr:link href="/shelter/" title="Shelter" uri="https://patrick.sirref.org/shelter/" display-uri="shelter" type="local">sounds familiar</fr:link>!).</html:p>
                    <html:p>I had some assumptions about the kinds of shell commands people use (myself included) when writing Dockerfiles. For example, making use of <html:code>AND</html:code>-lists for grouping commands in <html:code>RUN</html:code> instructions. But I had no evidence to back this up. So I ran some analysis over <fr:link href="/dockerfile2020henkel/" title="Learning from, Understanding, and Supporting DevOps Artifacts for Docker" uri="https://patrick.sirref.org/dockerfile2020henkel/" display-uri="dockerfile2020henkel" type="local">the dataset from this paper from Henkel et al.</fr:link>.</html:p>
                    <html:pre><![CDATA[Total Dockerfiles: 218_253 (errors: 808)
Total Docker Instructions: 2_729_118
Total RUN instructions: 657_909 (errors: 4_847)
========

Count (Windows Dockerfiles): 841 / 218_253
Count (ENV commands): 268_898 / 2_729_118
Count (COPY commands): 165_968 / 2_729_118
Count (ADD commands): 86_552 / 2_729_118
Count (WORKDIR commands): 104_112 / 2_729_118
Count (using at least one &&): 267_113 / 657_909
Count (uses of exec): 25 / 657_909
Count (system pkg manager: [apt, apt-get, dnf, apk, pacman, nix, brew, yum]): 192_092 / 657_909
Count (internet retrievers: [curl, wget]): 95_433 / 657_909
Count (might use python): 9_082 / 657_909
Counts:
[(rm, 169_386), (echo, 113_768), (mkdir, 100_924), (cd, 74_904),
 (chmod, 55_998), (set, 25_738), (mv, 24_034), (sed, 21_232),
 (chown, 18_610), (grep, 16_604), (cp, 15_637), (export, 13_154),
 ([, 10_411), (touch, 3_152), (exit, 2_288), (printf, 2_275), (awk, 2_010),
 (., 1_077), (test, 1_004), (source, 904), (pwd, 199), (unset, 105),
 (diff, 40), (trap, 2)]]]></html:pre>
                    <html:p>I think there's some evidence here to support some assumptions I already had:</html:p>
                    <html:ol>
                      <html:li>
                        <html:p>A significant proportion of <html:code>RUN</html:code> instructions in Dockerfiles invoke package managers.</html:p>
                      </html:li>
                      <html:li>
                        <html:p>A significant proportion of <html:code>RUN</html:code> instructions invoke <html:code>curl</html:code> or <html:code>wget</html:code>.</html:p>
                      </html:li>
                      <html:li>
                        <html:p>There is something rather <fr:link href="https://en.wikipedia.org/wiki/Zipf's_law" type="external">Zipfian</fr:link> about how shell built-in utilities are used.</html:p>
                      </html:li>
                      <html:li>
                        <html:p>There is limited used of <html:code>exec</html:code> (and therefore limited use of opening file descriptors directly via the shell, as opposed to a command running in a subprocess).</html:p>
                      </html:li>
                      <html:li>
                        <html:p>A lot of time is spent changing directories, either temporally (via <html:code>cd</html:code> in a <html:code>RUN</html:code> instruction, or by using <html:code>WORKDIR</html:code>).</html:p>
                      </html:li>
                      <html:li>
                        <html:p>Lots of <html:code>ENV</html:code> setting.</html:p>
                      </html:li>
                    </html:ol>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>9</fr:month>
                          <fr:day>12</fr:day>
                        </fr:date>
                        <fr:title text="Parsing Dockerfiles">Parsing Dockerfiles</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>For reasons that are probably quite clear, whenever I end up doing a small task like this, I always reach for OCaml to do it. The idea is that I'm invested in that community and hope to see it thrive. And if that means contributing some fixes, libraries or advice along the way, it seems to me that that is time well spent.</html:p>
                        <html:p>When I started, I assumed <fr:link href="https://ocaml.org/p/dockerfile/latest" type="external">ocaml-dockerfile</fr:link> had support for parsing files; it turns out it doesn't, but I found <fr:link href="/samoht/" title="Thomas Gazagnaire" uri="https://patrick.sirref.org/samoht/" display-uri="samoht" type="local">Thomas G.</fr:link>'s <fr:link href="https://tangled.org/gazagnaire.org/ocaml-dockerfile/" type="external">port</fr:link> that does. So I took it for a spin and the results were less than I had hoped for. Of the 219,061 Dockerfiles, 97,123 were not parseable! <fr:link href="https://tangled.org/gazagnaire.org/ocaml-dockerfile/pulls/1/round/0" type="external">After a series of commits</fr:link> I managed to get that number down to <html:code>808</html:code>.</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>9</fr:month>
                      <fr:day>12</fr:day>
                    </fr:date>
                    <fr:title text="Eio process environment">Eio process environment</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>What started out seemingly straightforward (add a capability-based API to <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> for accessing environment variables) ended up being a more nuanced discussion about what the best API would be. In the end, <fr:link href="/talex5/" title="Thomas Leonard" uri="https://patrick.sirref.org/talex5/" display-uri="talex5" type="local">Thomas</fr:link> added <fr:link href="https://github.com/ocaml-multicore/eio/pull/930" type="external">an <html:code>Eio.Process.Env</html:code> API</fr:link> to manipulate soon-to-be opaque environments. This paved the way for the <fr:link href="https://github.com/ocaml-multicore/eio/pull/923" type="external">portable environment support</fr:link>.</html:p>
                    <html:p>The API <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> exposes is readonly so we do not have to worry too much about race conditions (even though users are free to <html:code>Unix.putenv</html:code>).</html:p>
                    <html:p>One quirk of this work was reaffirming something I had already stumbled across in <fr:link href="/merry-benchmarks/" title="Shell Benchmarks, Back to Writing › Benchmarking Merry " uri="https://patrick.sirref.org/merry-benchmarks/" display-uri="merry-benchmarks" type="local">Merry</fr:link>. Accessing and searching large environments has a cost! For my rather large Nix environment, we can see the slowdown pretty clearly.</html:p>
                    <html:pre><![CDATA[                  name,   major-allocated,   minor-allocated,   monotonic-clock
        get_env/all 10,        346.619865,      38577.227215,      29248.828738
        get_env/all 50,       1677.003302,     181788.755923,     138307.102538
       get_env/all 100,       3323.770915,     361218.802888,     274768.577259
   get_env/all_once 10,        121.955078,      14694.900546,      11652.096583
   get_env/all_once 50,        563.133065,      61951.683658,      48161.605588
  get_env/all_once 100,       1113.238600,     121431.915770,      93960.646400
     get_env/direct 10,          0.000000,          0.000000,        974.649587
     get_env/direct 50,          0.000000,          0.000000,       4369.486360
    get_env/direct 100,          0.000000,          0.000000,       8753.393503]]></html:pre>
                    <html:p>The <html:code>all</html:code> benchmark calls <html:code>environment</html:code> every time and then finds a variable using <html:code>Eio.Process.Env.get_opt</html:code>, <html:code>all_once</html:code> calls <html:code>environment</html:code> once and then <html:code>Eio.Process.Env.get_opt</html:code> every time and <html:code>direct</html:code> uses the <html:code>Eio.Process.get_opt</html:code> directly every time. The differences are a lot less stark when the environment is pretty small.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>9</fr:month>
                      <fr:day>12</fr:day>
                    </fr:date>
                    <fr:title text="Ppxlib Migrations">Ppxlib Migrations</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>Thankfully, <fr:link href="/ppxlib/" title="Ppxlib" uri="https://patrick.sirref.org/ppxlib/" display-uri="ppxlib" type="local">ppxlib</fr:link> has been mostly stable for a number of months. In part, thanks to the diligent efforts of <fr:link href="/nathanreb/" title="Nathan Rebours" uri="https://patrick.sirref.org/nathanreb/" display-uri="nathanreb" type="local">Nathan</fr:link>. However, <fr:link href="/dra27/" title="David Allsopp" uri="https://patrick.sirref.org/dra27/" display-uri="dra27" type="local">David</fr:link> uncovered some unpleasant bugs in the migration code for some new 5.4 AST features. Unfortunately, it is only for attributes which are a source of pain for <fr:link href="/ppxlib/" title="Ppxlib" uri="https://patrick.sirref.org/ppxlib/" display-uri="ppxlib" type="local">ppxlib</fr:link>. I spent some time reviewing the fixes, and it looks like we still need some more work to ensure the attributes attached to package types roundtrip cleanly.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>9</fr:month>
                      <fr:day>12</fr:day>
                    </fr:date>
                    <fr:title text="Misc.">Misc.</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>Some other work I recorded separately and include here for the eager reader.</html:p>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>7</fr:month>
                          <fr:day>12</fr:day>
                        </fr:date>
                        <fr:uri>https://patrick.sirref.org/ocaml-roundup-august-2026/</fr:uri>
                        <fr:display-uri>ocaml-roundup-august-2026</fr:display-uri>
                        <fr:route>/ocaml-roundup-august-2026/</fr:route>
                        <fr:title text="OCaml Roundup: August 2026">OCaml Roundup: August 2026</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>Much of August was spent thinking about POSIX shell semantics, and in particular extending <fr:link href="/greenberg2020smoosh/" title="Executable formal semantics for the POSIX shell" uri="https://patrick.sirref.org/greenberg2020smoosh/" display-uri="greenberg2020smoosh" type="local">notions from smoosh</fr:link> to include side effects (and maybe concurrency). This is still nascent work, but some ideas and flavours are described below.</html:p>
                        <fr:tree show-metadata="false" numbered="false">
                          <fr:frontmatter>
                            <fr:authors>
                              <fr:author>
                                <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                              </fr:author>
                            </fr:authors>
                            <fr:date>
                              <fr:year>2026</fr:year>
                              <fr:month>7</fr:month>
                              <fr:day>12</fr:day>
                            </fr:date>
                            <fr:title text="Merging Shells">Merging Shells</fr:title>
                          </fr:frontmatter>
                          <fr:mainmatter>
                            <html:p>The work came from wanting to merge two shell sessions into one. Setting aside the practicalities of an implementation, it makes sense to start first theoretically. When can I merge two shell executions, and when can I not?</html:p>
                            <html:div style="text-align: center;">
<html:img height="400px" src="/bafkrmibyvel7ut7rkmhcagizkntkh2hz5urf7rzif7py5zh7a4nntrv5u4.png" />
</html:div>
                            <html:p>It would be nice to think of <html:code>unset DOWNLOAD_DIR</html:code> as a function. This function takes the shell state and the operating state as arguments and returns a modified shell state and an "unmodified" operating system state (at least for our purposes). Meanwhile, <html:code>branch-b</html:code> may have a lot more side effects, but when focusing on the environment only we can see in a sense the two branches are conflict free. Well, sorta. We also need to ensure we do not also read <html:code>DOWNLOAD_DIR</html:code> in <html:code>branch-b</html:code> if we intend to remove it in their merge.</html:p>
                            <html:div style="text-align: center;">
<html:img height="300px" src="/bafkrmibympwhn47ko6ogugzj7kcqo7uqoo2xzjqrfxmcems27umlkfigeq.png" />
</html:div>
                            <html:p>This work, in part, is borrowing ideas from <fr:link href="/kcrsk-mrdts-2022/" title="Certified mergeable replicated data types" uri="https://patrick.sirref.org/kcrsk-mrdts-2022/" display-uri="kcrsk-mrdts-2022" type="local">MRDTs</fr:link> and can reuse <fr:link href="/void-process/" title="Void Process" uri="https://patrick.sirref.org/void-process/" display-uri="void-process" type="local">void processes</fr:link> to help reason about when side effects can occur, as well as <fr:link href="/open-trace/" title="Opentrace" uri="https://patrick.sirref.org/open-trace/" display-uri="open-trace" type="local">open-trace</fr:link> (which is now becoming <html:code>os-trace</html:code>).</html:p>
                            <html:p>I hope to continue this work, see my <fr:link href="/weekly-2026-w37/" title="Analysing some Dockerfiles" uri="https://patrick.sirref.org/weekly-2026-w37/" display-uri="weekly-2026-w37" type="local">latest weekly</fr:link> for more on this.</html:p>
                          </fr:mainmatter>
                        </fr:tree>
                        <fr:tree show-metadata="false" numbered="false">
                          <fr:frontmatter>
                            <fr:authors>
                              <fr:author>
                                <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                              </fr:author>
                            </fr:authors>
                            <fr:date>
                              <fr:year>2026</fr:year>
                              <fr:month>7</fr:month>
                              <fr:day>12</fr:day>
                            </fr:date>
                            <fr:title text="Eio in the browser">Eio in the browser</fr:title>
                          </fr:frontmatter>
                          <fr:mainmatter>
                            <html:p>I spent some time carving out <html:code>Eio_browser</html:code> into a standalone library, something I depend on for <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> to work <fr:link href="https://patrick.sirref.org/msh" type="external">in the browser</fr:link>.</html:p>
                            <fr:tree show-metadata="false" numbered="false">
                              <fr:frontmatter>
                                <fr:authors>
                                  <fr:author>
                                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                                  </fr:author>
                                </fr:authors>
                                <fr:date>
                                  <fr:year>2026</fr:year>
                                  <fr:month>9</fr:month>
                                  <fr:day>12</fr:day>
                                </fr:date>
                                <fr:uri>https://patrick.sirref.org/eio_browser/</fr:uri>
                                <fr:display-uri>eio_browser</fr:display-uri>
                                <fr:route>/eio_browser/</fr:route>
                                <fr:title text="Eio_browser">Eio_browser</fr:title>
                                <fr:meta name="external">https://git.sirref.org/eio_browser</fr:meta>
                              </fr:frontmatter>
                              <fr:mainmatter>
                                <html:p><html:code>Eio_browser</html:code> is a backend for <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> that works in the browser. It <fr:link href="https://github.com/ocaml-multicore/eio_js" type="external">reuses work I started</fr:link> at <fr:link href="https://patrick.sirref.org/tarides/" type="external">Tarides</fr:link> that picked up some momentum but has since been a little dormant.</html:p>
                                <html:p>Crucially, it provides a functional standard environment like <html:code>Eio_main</html:code> or any of the OS-specific backends.</html:p>
                                <html:pre class="hilite">
                                  <html:code>
                                    <html:span class="ocaml-keyword-other">module</html:span>
                                    <html:span class="ocaml-source"> </html:span>
                                    <html:span class="ocaml-constant-language-capital-identifier">Stdenv</html:span>
                                    <html:span class="ocaml-source"> </html:span>
                                    <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                                    <html:span class="ocaml-source"> </html:span>
                                    <html:span class="ocaml-keyword-other">sig</html:span>
                                    <html:span class="ocaml-source">
</html:span>
                                    <html:span class="ocaml-source">  </html:span>
                                    <html:span class="ocaml-keyword">type</html:span>
                                    <html:span class="ocaml-source"> </html:span>
                                    <html:span class="ocaml-entity-name-function-binding">base</html:span>
                                    <html:span class="ocaml-source"> =
</html:span>
                                    <html:span class="ocaml-source">    &lt; stdout : Eio_unix.sink_ty r
</html:span>
                                    <html:span class="ocaml-source">    ; stderr : Eio_unix.sink_ty r
</html:span>
                                    <html:span class="ocaml-source">    ; clock : float Eio.Time.clock_ty r
</html:span>
                                    <html:span class="ocaml-source">    ; mono_clock : Eio.Time.Mono.ty r
</html:span>
                                    <html:span class="ocaml-source">    ; secure_random : Eio.Flow.source_ty r
</html:span>
                                    <html:span class="ocaml-source">    ; debug : Eio.Debug.t
</html:span>
                                    <html:span class="ocaml-source">    ; backend_id : string &gt;
</html:span>
                                    <html:span class="ocaml-keyword-other">end</html:span>
                                    <html:span class="ocaml-source">
</html:span>
                                    <html:span class="ocaml-source">
</html:span>
                                    <html:span class="ocaml-keyword-other">val</html:span>
                                    <html:span class="ocaml-source"> </html:span>
                                    <html:span class="ocaml-source">run</html:span>
                                    <html:span class="ocaml-source"> </html:span>
                                    <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                                    <html:span class="ocaml-source"> </html:span>
                                    <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                                    <html:span class="ocaml-constant-language-capital-identifier">Stdenv</html:span>
                                    <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                                    <html:span class="ocaml-source">base</html:span>
                                    <html:span class="ocaml-source"> </html:span>
                                    <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                                    <html:span class="ocaml-source"> </html:span>
                                    <html:span class="ocaml-support-type">unit</html:span>
                                    <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                                    <html:span class="ocaml-source"> </html:span>
                                    <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                                    <html:span class="ocaml-source"> </html:span>
                                    <html:span class="ocaml-support-type">unit</html:span>
                                    <html:span class="ocaml-source">
</html:span>
                                    <html:span class="ocaml-comment-doc"><![CDATA[(**]]></html:span>
                                    <html:span class="ocaml-comment-doc"><![CDATA[ [run fn] runs [fn] passing it the {! Stdenv.base} environment.]]></html:span>
                                    <html:span class="ocaml-comment-doc">
</html:span>
                                    <html:span class="ocaml-comment-doc"><![CDATA[    See {! Backend.run} for more information about how effects are handled.]]></html:span>
                                    <html:span class="ocaml-comment-doc"><![CDATA[*)]]></html:span>
                                    <html:span class="ocaml-source">
</html:span>
                                  </html:code>
                                </html:pre>
                              </fr:mainmatter>
                            </fr:tree>
                          </fr:mainmatter>
                        </fr:tree>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>8</fr:month>
                  <fr:day>17</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w33/</fr:uri>
                <fr:display-uri>weekly-2026-w33</fr:display-uri>
                <fr:route>/weekly-2026-w33/</fr:route>
                <fr:title text="Browsher and Namespaces">Browsher and Namespaces</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>The work presented constitutes a good bit more than a weeks worth of work. That being said, let's dive in.</html:p>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>8</fr:month>
                      <fr:day>17</fr:day>
                    </fr:date>
                    <fr:uri>https://patrick.sirref.org/browsher/</fr:uri>
                    <fr:display-uri>browsher</fr:display-uri>
                    <fr:route>/browsher/</fr:route>
                    <fr:title text="Browsher ">Browsher </fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>After a week or two of abstracting the internals of <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>, I finally got a shell running in the browser. You can have a play with it here:</html:p>
                    <html:p>
                      <fr:link href="https://patrick.sirref.org/msh" type="external">https://patrick.sirref.org/msh</fr:link>
                    </html:p>
                    <html:p>The final hurdle was providing an abstract interface to file handles. <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link>, the library that so far has enabled much of the OS abstraction, doesn't have a particularly good cross-platform file descriptor API. <fr:link href="https://ocaml-multicore.github.io/eio/eio/Eio_unix/index.html" type="external">Eio_unix</fr:link> does extend sources and sinks with the <html:code>Unix_fd</html:code> tag, but there is no <html:code>Eio.Fd.dup2</html:code> that provides a platform-agnostic API for file handle manipulation. Even if it did, a POSIX shell does a fair amount of assuming that your file handles are integers which might prove tricky on Windows.</html:p>
                    <html:p>The <fr:link href="https://patrick.sirref.org/msh" type="external">shell in the browser</fr:link> completely reuses the logic of <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>. It also links in a <fr:link href="https://git.sirref.org/bruit/tree/src/brr/bruit_brr.ml?h=web" type="external">browser-aware subpackage of bruit</fr:link> (the OCaml port of linenoise I wrote) and a few, half-baked POSIX utilities I wrote (like <html:code>ls</html:code> and <html:code>cat</html:code>). But you can at least run something like:</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="sh-source">$ </html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">hello</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-keyword-operator-redirect">&gt;</html:span>
                        <html:span class="sh-source"> hello.txt
</html:span>
                        <html:span class="sh-source">$ ls -la </html:span>
                        <html:span class="sh-support-function-builtin">.</html:span>
                        <html:span class="sh-source">
</html:span>
                        <html:span class="sh-source">$ cat </html:span>
                        <html:span class="sh-keyword-operator-redirect">&lt;</html:span>
                        <html:span class="sh-source"> hello.txt
</html:span>
                      </html:code>
                    </html:pre>
                    <html:p>Being able to, in some sense, run a shell <html:em>anywhere</html:em> is actually quite an attractive proposition. Shells, with all of their wonderful footguns, are still a reliable and familiar tool for interacting with a running system. I'm interested in the idea of running shells in more exotic deployments (e.g., unikernels) where it becomes an interface for dynamical reconfiguration or a tool for debugging.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>8</fr:month>
                      <fr:day>17</fr:day>
                    </fr:date>
                    <fr:title text="Updates on Eio_mem">Updates on Eio_mem</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>At the heart of <fr:link href="/browsher/" title="Browsher and Namespaces › Browsher " uri="https://patrick.sirref.org/browsher/" display-uri="browsher" type="local">browsher</fr:link> is <fr:link href="/eio_mem/" title="Three weeks in the Shell › An In-memory Filesystem for Eio " uri="https://patrick.sirref.org/eio_mem/" display-uri="eio_mem" type="local">Eio_mem</fr:link> (an in-memory filesystem for <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link>). The <fr:link href="/eio_mem/" title="Three weeks in the Shell › An In-memory Filesystem for Eio " uri="https://patrick.sirref.org/eio_mem/" display-uri="eio_mem" type="local">Eio_mem</fr:link> API now comes with (and exposes) a number of file descriptor operations:</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="ocaml-keyword-other">val</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">pipe</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Switch</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">t</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Fd</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">t</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">*</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Fd</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">t</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-keyword-other">val</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">dup</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">sw</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Switch</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">t</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Fd</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">t</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Fd</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">t</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-keyword-other">val</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">dup2</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">sw</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Switch</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">t</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">src</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Fd</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">t</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">tgt</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Fd</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">t</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-support-type">unit</html:span>
                        <html:span class="ocaml-source">
</html:span>
                      </html:code>
                    </html:pre>
                    <html:p>I was happy to see <fr:link href="/eio_mem/" title="Three weeks in the Shell › An In-memory Filesystem for Eio " uri="https://patrick.sirref.org/eio_mem/" display-uri="eio_mem" type="local">Eio_mem</fr:link> getting some use in <fr:link href="/samoht/" title="Thomas Gazagnaire" uri="https://patrick.sirref.org/samoht/" display-uri="samoht" type="local">Thomas G.</fr:link>'s fork <fr:link href="https://tangled.org/gazagnaire.org/ocaml-eio-fs" type="external">eio-fs</fr:link> where he was able to use it for <fr:link href="https://tangled.org/gazagnaire.org/ocaml-eio-fs/blob/main/lib/chaos.mli" type="external">chaos engineering</fr:link>.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>8</fr:month>
                      <fr:day>17</fr:day>
                    </fr:date>
                    <fr:uri>https://patrick.sirref.org/namespace-musings/</fr:uri>
                    <fr:display-uri>namespace-musings</fr:display-uri>
                    <fr:route>/namespace-musings/</fr:route>
                    <fr:title text="Namespaces ">Namespaces </fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>I spent a few days recently trying to come up with some formalism for the notion of names, namespaces and name resolution.</html:p>
                    <html:p>On the surface, it seems <fr:link href="/bigraphs2003milner/" title="Bigraphs and transitions" uri="https://patrick.sirref.org/bigraphs2003milner/" display-uri="bigraphs2003milner" type="local">bigraphs</fr:link> might be a fruitful approach. The orthogonal treatment of <html:em>locality</html:em> and <html:em>connectivity</html:em> maps fairly well onto <html:em>name structure</html:em> (e.g, <html:code>Module.X.foo</html:code>, <html:code>/foo/bar/hello.txt</html:code> or <html:code>foo.example.org</html:code>) and <html:em>redirection</html:em> (e.g., symlinks or CNAMEs). Bigraph reaction rules could then model name resolution. A resolution process can move through the hierarchical name structure and across links at which point you might have some interesting properties to prove or check about a particular namespace system. Additionally, namespaces and resolution mechanism might compose in different ways and this might be worth formalising and comparing.</html:p>
                    <html:p>I didn't get much further than modelling some bits of a filesystem in <fr:link href="/bigrapher/" title="BigraphER" uri="https://patrick.sirref.org/bigrapher/" display-uri="bigrapher" type="local">BigraphER</fr:link>.</html:p>
                    <html:pre><![CDATA[# filesystem
ctrl Dir = 0;
atomic ctrl File = 1;
atomic ctrl SymLink = 2;

# Query -- the thing that gets resolved.
atomic ctrl Q = 1;

# Reaction Rules
react resolve_symlink =
  Q{x} | SymLink{x, y} --> Q{y} | SymLink{x, y};

# Initial State
big s0 =
  Dir.
    (SymLink{sym_a, file_a}
     |
     Q{sym_a}
     |
     File{file_a}
    );

# Bigraphical Reactive System
begin brs 
  init s0;
  rules = [ {resolve_symlink} ];
end]]></html:pre>
                    <html:p>Resulting in these two transition states.</html:p>
                    <html:div style="text-align: center;">
<html:img height="250px" src="/bafkrmibvtr7mur4f2xrtts24pnpev5gltcy526y6whmwqdwxcbyqbuvuq4.svg" />
<html:img height="250px" src="/bafkrmia6f6cn3zr7rq4qjr2t3jjbryr6dvbeldnse6kvaweyoiwbrnewre.svg" />
</html:div>
                    <html:p>Bear in mind this was just a quick experiment. The first bigraph shows our query at <html:code>sym_a</html:code> and the second bigraph (using the <html:code>resolve_symlink</html:code> reduction rule) shows the query at <html:code>file_a</html:code>. All of this takes places within <html:code>Dir</html:code>. It seems that <fr:link href="/bigrapher/" title="BigraphER" uri="https://patrick.sirref.org/bigrapher/" display-uri="bigrapher" type="local">BigraphER</fr:link> represents the tree structure graphically with black arrows (rather than spatial containment).</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>8</fr:month>
                      <fr:day>17</fr:day>
                    </fr:date>
                    <fr:title text="Papers">Papers</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>I'm working on a paper right now for <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> and <fr:link href="/shelter/" title="Shelter" uri="https://patrick.sirref.org/shelter/" display-uri="shelter" type="local">Shelter</fr:link>. It is mostly coming together okay; and I am enjoying the creative aspects of this paper, those things with no right answer just "hey, I think this is a good/useful/fun idea".</html:p>
                    <html:p>My TikZ-abilities have also had a refresher.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>8</fr:month>
                      <fr:day>17</fr:day>
                    </fr:date>
                    <fr:title text="TODOs">TODOs</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>Over the next week, I hope to finish up a first draft of the aforementioned paper. I also want to publish a backlog of packages I have for OCaml: bruit, ocaml-bibtex, a fresh cut of <fr:link href="/graft/" title="Graft" uri="https://patrick.sirref.org/graft/" display-uri="graft" type="local">Graft</fr:link> etc.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>8</fr:month>
                      <fr:day>17</fr:day>
                    </fr:date>
                    <fr:title text="Ramblings">Ramblings</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>I had some half-formed thoughts lying around in my forest, so I thought I might include those here:</html:p>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>8</fr:month>
                          <fr:day>11</fr:day>
                        </fr:date>
                        <fr:uri>https://patrick.sirref.org/on-boilerplate/</fr:uri>
                        <fr:display-uri>on-boilerplate</fr:display-uri>
                        <fr:route>/on-boilerplate/</fr:route>
                        <fr:title text="Boilerplate">Boilerplate</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>The word <fr:link href="https://www.merriam-webster.com/dictionary/boilerplate" type="external"><html:em>boilerplate</html:em></fr:link> gained popularity as a colloquialism used by newspaper and magazine editors for pre-prepared editorials and stories that were syndicated across the US. In effect, boilerplates were news stamps that could produce identical content.</html:p>
                        <html:p>I have been thinking about the nature of <html:em>boilerplate code</html:em>. How is it similar or different to boilerplate in its original meaning.</html:p>
                        <html:p>Distinguishing true boilerplate code from code that feels repetitive or boring to write is, I think, important. Argument parsing falls into this category quite well. Consider the following flag I added to <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>'s <html:code>set</html:code> built-in.</html:p>
                        <html:pre class="hilite">
                          <html:code>
                            <html:span class="ocaml-keyword">let</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-entity-name-function-binding">all_export</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">=</html:span>
                            <html:span class="ocaml-source">
</html:span>
                            <html:span class="ocaml-source">  </html:span>
                            <html:span class="ocaml-keyword">let</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-entity-name-function-binding">doc</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">=</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-string-quoted-double">Export all variables, like -o allexport.</html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-other">in</html:span>
                            <html:span class="ocaml-source">
</html:span>
                            <html:span class="ocaml-source">  </html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Arg</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                            <html:span class="ocaml-source">value</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">&amp;</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">flag</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">&amp;</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">info</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source"><![CDATA[[]]></html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-string-quoted-double">a</html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source"><![CDATA[]]]></html:span>
                            <html:span class="ocaml-source"> ~</html:span>
                            <html:span class="ocaml-source">docv</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-string-quoted-double">ALLEXPORT</html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-source"> ~</html:span>
                            <html:span class="ocaml-source">doc</html:span>
                            <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                            <html:span class="ocaml-source">
</html:span>
                          </html:code>
                        </html:pre>
                        <html:p>It uses <fr:link href="https://erratique.ch/software/cmdliner" type="external">cmdliner</fr:link> to define a new <html:code>-a</html:code> flag that will, in effect, make all variable definitions act like the <html:code>export</html:code> built-in. <fr:link href="https://anil.recoil.org/notes/claude-copilot-sandbox#generating-higher-level-ocaml-interfaces-and-docstrings" type="external">LLMs have been praised for their ability to do this specific task in OCaml</fr:link>.</html:p>
                        <html:p>What exactly is boilerplate about this code snippet? It seems to me that there is some sort of ceremony to defining a flag. There is boilerplate in the structure with more meaningful content threaded through. Below, is the example reduced to just the boilerplate replacing the for more meaningful parts with <fr:link href="https://ocaml.github.io/merlin/editor/vim/#expression-construction" type="external">Merlin's typed holes</fr:link>: <html:code>_</html:code>.</html:p>
                        <html:pre class="hilite">
                          <html:code>
                            <html:span class="ocaml-keyword">let</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-entity-name-function-binding">all_export</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">=</html:span>
                            <html:span class="ocaml-source">
</html:span>
                            <html:span class="ocaml-source">  </html:span>
                            <html:span class="ocaml-keyword">let</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-entity-name-function-binding">doc</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">=</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-constant-language">_</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-other">in</html:span>
                            <html:span class="ocaml-source">
</html:span>
                            <html:span class="ocaml-source">  </html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Arg</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                            <html:span class="ocaml-source">value</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">&amp;</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">flag</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">&amp;</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">info</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source"><![CDATA[[]]></html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-constant-language">_</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source"><![CDATA[]]]></html:span>
                            <html:span class="ocaml-source"> ~</html:span>
                            <html:span class="ocaml-source">docv</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                            <html:span class="ocaml-constant-language">_</html:span>
                            <html:span class="ocaml-source"> ~</html:span>
                            <html:span class="ocaml-source">doc</html:span>
                            <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                            <html:span class="ocaml-source">
</html:span>
                          </html:code>
                        </html:pre>
                        <html:p>Arguably the use of <html:code>flag</html:code> is quite important too as that is where the magic happens if you want an optional argument, or a positional one etc. As a thought experiment, having agents deliver fill-in-the-blanks style code might be quite useful. In a type system that can support holes (e.g. like in <fr:link href="https://patrick.sirref.org/ocaml/" type="external">OCaml</fr:link> or <fr:link href="/hazel/" title="Hazel" uri="https://patrick.sirref.org/hazel/" display-uri="hazel" type="local">Hazel</fr:link>) it becomes a more pleasant exercise, but helps keep you in the loop. I, myself, do not use any AI in the code I write; so I admit that I am pretty far from the problem.</html:p>
                        <html:p>The problem I have with this approach is that it is fairly easy to automate in a predictable and easy way! I just added a <html:code>snippet</html:code> subcommand to <fr:link href="/octx/" title="OCaml Toolbox" uri="https://patrick.sirref.org/octx/" display-uri="octx" type="local"><html:code>octx</html:code></fr:link>, but you could easily wrap this up in <fr:link href="https://github.com/joaotavora/yasnippet" type="external">whatever your editor of choice is</fr:link>.</html:p>
                        <html:pre><![CDATA[$ octx snippet cmdliner-flag
let cmdliner_flag =
  let doc = _ in
  Arg.(value & flag & info [ _ ] ~docv:_ ~doc)]]></html:pre>
                        <html:p>The more I think on this problem the harder I find good uses for LLMs besides churning out vasts amount of end-to-end code; something I am personally not that interested in doing. It seems to me, to reap the benefits of LLMs, one must relinquish a certain amount of agency; anything less, and you could easily replace its usage with a cheaper, more predictable tool.</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>7</fr:month>
                  <fr:day>4</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w26/</fr:uri>
                <fr:display-uri>weekly-2026-w26</fr:display-uri>
                <fr:route>/weekly-2026-w26/</fr:route>
                <fr:title text="Shell Benchmarks, Back to Writing">Shell Benchmarks, Back to Writing</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>A little late due to travel, but I did a little benchmarking of <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>.</html:p>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>7</fr:month>
                      <fr:day>4</fr:day>
                    </fr:date>
                    <fr:title text="Signal Handling in Merry">Signal Handling in Merry</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>In order to even run <fr:link href="https://github.com/shellspec/shellbench" type="external">shellbench</fr:link>, I first had to fix some bugs in the shell implementation. Shellbench aims to warm up the shell before starting any benchmarks; it includes this prelude before getting started:</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="sh-source">__ready=
</html:span>
                        <html:span class="sh-support-function-builtin">trap</html:span>
                        <html:span class="sh-source"> __ready=1 HUP
</html:span>
                        <html:span class="sh-support-function-builtin">kill</html:span>
                        <html:span class="sh-source"> -HUP </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">MAIN_PID</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-source">
</html:span>
                        <html:span class="sh-keyword-control">until</html:span>
                        <html:span class="sh-meta-scope-while-loop"><![CDATA[ []]></html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">__ready</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-meta-scope-while-loop"><![CDATA[ ]]]></html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-while-loop"> </html:span>
                        <html:span class="sh-keyword-control">do</html:span>
                        <html:span class="sh-meta-scope-while-loop"> __dummy=</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-while-loop"> </html:span>
                        <html:span class="sh-keyword-control">done</html:span>
                        <html:span class="sh-source">
</html:span>
                      </html:code>
                    </html:pre>
                    <html:p>Whilst writing <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>, I hadn't really considered that the return context from a trap needed to be taken into consideration! I had assumed they would be silently ignored, how else would we know where to thread that context back into the shell's execution.</html:p>
                    <html:p>Even after reading through the specification for <fr:link href="https://pubs.opengroup.org/onlinepubs/009604599/utilities/trap.html" type="external">trap</fr:link> I am still none the wiser. I've opted to place some sensible signal polling points during the execution of the commands to run any signals that have come in. The single handlers in <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> simply push thunks onto a run queue. Looking at the Dash source code, it <fr:link href="https://git.kernel.org/pub/scm/utils/dash/dash.git/tree/src/eval.c#n230" type="external">seems to be something that happens just before evaluation</fr:link>.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>7</fr:month>
                      <fr:day>4</fr:day>
                    </fr:date>
                    <fr:uri>https://patrick.sirref.org/merry-benchmarks/</fr:uri>
                    <fr:display-uri>merry-benchmarks</fr:display-uri>
                    <fr:route>/merry-benchmarks/</fr:route>
                    <fr:title text="Benchmarking Merry ">Benchmarking Merry </fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>7</fr:month>
                          <fr:day>4</fr:day>
                        </fr:date>
                        <fr:title text="Medium-to-small potatoes">Medium-to-small potatoes</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>I used <fr:link href="https://github.com/shellspec/shellbench" type="external">shellbench</fr:link> to understand where some of the performance pitfalls were coming from.</html:p>
                        <html:pre class="hilite">
                          <html:code>
                            <html:span class="sh-source">$ ./shellbench -s bash,zsh,msh sample/func.sh
</html:span>
                            <html:span class="sh-source">-----------------------------------------------------------------------------------
</html:span>
                            <html:span class="sh-source">name                                                     bash        zsh        msh
</html:span>
                            <html:span class="sh-source">-----------------------------------------------------------------------------------
</html:span>
                            <html:span class="sh-source">func.sh: no func                                      775,539  1,124,485      5,229
</html:span>
                            <html:span class="sh-source">func.sh: func                                         398,861    197,169     93,424
</html:span>
                            <html:span class="sh-source">-----------------------------------------------------------------------------------
</html:span>
                            <html:span class="sh-keyword-operator-glob">*</html:span>
                            <html:span class="sh-source"> count: number of executions per second
</html:span>
                          </html:code>
                        </html:pre>
                        <html:p>This particular benchmark first runs nothing (i.e. <html:code>:</html:code>) and then a function lookup and execution. Surprisingly <html:code>msh</html:code> is <html:em>slower</html:em> at doing <html:em>nothing</html:em>! I <html:code>perf</html:code>-ed a run of <html:code>msh test.sh</html:code> with just a single <html:code>:</html:code> in the file:</html:p>
                        <html:iframe width="100%|" height="400px" src="/bafkrmicucn54u6lmz2u3jky3sug4nxj7yhwjf3iscpfcax7ar4fwjl7gce.svg" />
                        <fr:tree show-metadata="false" numbered="false">
                          <fr:frontmatter>
                            <fr:authors>
                              <fr:author>
                                <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                              </fr:author>
                            </fr:authors>
                            <fr:date>
                              <fr:year>2026</fr:year>
                              <fr:month>7</fr:month>
                              <fr:day>4</fr:day>
                            </fr:date>
                            <fr:title text="Test built-in">Test built-in</fr:title>
                          </fr:frontmatter>
                          <fr:mainmatter>
                            <html:p>After making various improvements to <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> that almost halved the memory usage (e.g., removing extraneous string manipulations, adding a fast-path to the <html:code>PATH</html:code> variable for resolving programs). I was still pretty far off the performance of these other shells. I decided to have a look at <html:code>dash</html:code> which has a simple implementation.</html:p>
                            <html:iframe width="100%|" height="400px" src="/bafkrmieljhv7prkf5rovr72f22v44fdizrszrmtn46feydg6phek2sac7a.svg" />
                            <html:p>This flame graph is for the following shell script:</html:p>
                            <html:pre class="hilite">
                              <html:code>
                                <html:span class="sh-entity-name-function">f</html:span>
                                <html:span class="sh-meta-function"> </html:span>
                                <html:span class="sh-punctuation-definition-arguments"><![CDATA[()]]></html:span>
                                <html:span class="sh-meta-function"> </html:span>
                                <html:span class="sh-punctuation-definition-group"><![CDATA[{]]></html:span>
                                <html:span class="sh-meta-scope-group"> </html:span>
                                <html:span class="sh-support-function-builtin">:</html:span>
                                <html:span class="sh-keyword-operator-list">;</html:span>
                                <html:span class="sh-meta-scope-group"> </html:span>
                                <html:span class="sh-punctuation-definition-group"><![CDATA[}]]></html:span>
                                <html:span class="sh-punctuation-definition-function">
</html:span>
                                <html:span class="sh-keyword-control">while</html:span>
                                <html:span class="sh-meta-scope-while-loop"> __count=</html:span>
                                <html:span class="sh-punctuation-definition-string-begin"><![CDATA[$((]]></html:span>
                                <html:span class="sh-punctuation-definition-variable">$</html:span>
                                <html:span class="sh-variable-other-normal">__count</html:span>
                                <html:span class="sh-keyword-operator-arithmetic">+</html:span>
                                <html:span class="sh-constant-numeric-integer">1</html:span>
                                <html:span class="sh-punctuation-definition-string-end"><![CDATA[))]]></html:span>
                                <html:span class="sh-meta-scope-while-loop"> </html:span>
                                <html:span class="sh-keyword-operator-list">&amp;&amp;</html:span>
                                <html:span class="sh-meta-scope-while-loop"><![CDATA[ []]></html:span>
                                <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                                <html:span class="sh-punctuation-definition-variable">$</html:span>
                                <html:span class="sh-variable-other-normal">__count</html:span>
                                <html:span class="sh-punctuation-definition-string-end">"</html:span>
                                <html:span class="sh-meta-scope-while-loop"><![CDATA[ -le 10000 ]]]></html:span>
                                <html:span class="sh-keyword-operator-list">;</html:span>
                                <html:span class="sh-meta-scope-while-loop"> </html:span>
                                <html:span class="sh-keyword-control">do</html:span>
                                <html:span class="sh-meta-scope-while-loop">
</html:span>
                                <html:span class="sh-meta-scope-while-loop">	f
</html:span>
                                <html:span class="sh-keyword-control">done</html:span>
                                <html:span class="sh-source">
</html:span>
                                <html:span class="sh-support-function-builtin">echo</html:span>
                                <html:span class="sh-source"> </html:span>
                                <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                                <html:span class="sh-string-quoted-double">count is </html:span>
                                <html:span class="sh-punctuation-definition-variable">$</html:span>
                                <html:span class="sh-variable-other-normal">__count</html:span>
                                <html:span class="sh-punctuation-definition-string-end">"</html:span>
                                <html:span class="sh-source">
</html:span>
                              </html:code>
                            </html:pre>
                            <html:p>And here are some rudimentary benchmarks:</html:p>
                            <html:pre class="hilite">
                              <html:code>
                                <html:span class="sh-source">$ </html:span>
                                <html:span class="sh-keyword-other">time</html:span>
                                <html:span class="sh-source"> -p dash test.sh
</html:span>
                                <html:span class="sh-source">count is 10001
</html:span>
                                <html:span class="sh-source">real 0.01
</html:span>
                                <html:span class="sh-source">user 0.01
</html:span>
                                <html:span class="sh-source">sys 0.00
</html:span>
                                <html:span class="sh-source">$ </html:span>
                                <html:span class="sh-keyword-other">time</html:span>
                                <html:span class="sh-source"> -p msh test.sh
</html:span>
                                <html:span class="sh-source">count is 10001
</html:span>
                                <html:span class="sh-source">real 15.53
</html:span>
                                <html:span class="sh-source">user 4.28
</html:span>
                                <html:span class="sh-source">sys 12.28
</html:span>
                              </html:code>
                            </html:pre>
                            <html:p>It turns out the massive slowdown is very simple to explain. In the <html:code>dash</html:code> flamegraph there is a <html:code>oexpr</html:code> stack; <fr:link href="https://git.kernel.org/pub/scm/utils/dash/dash.git/tree/src/bltin/test.c" type="external">which comes from the bltin/test.c</fr:link> file. <html:code>dash</html:code>, along with the other shells, does <html:em>not</html:em> spawn a process for the <html:code><![CDATA[[]]></html:code>-test command! Forcing <html:code>dash</html:code> to do use the binary <html:code><![CDATA[[]]></html:code> gives:</html:p>
                            <html:pre class="hilite">
                              <html:code>
                                <html:span class="sh-source">$ </html:span>
                                <html:span class="sh-keyword-other">time</html:span>
                                <html:span class="sh-source"> -p dash test.sh
</html:span>
                                <html:span class="sh-source">count is 10001
</html:span>
                                <html:span class="sh-source">real 7.55
</html:span>
                                <html:span class="sh-source">user 3.95
</html:span>
                                <html:span class="sh-source">sys 3.44
</html:span>
                              </html:code>
                            </html:pre>
                            <html:p>Which is much more respectable in terms of performance compared to <html:code>msh</html:code>. Plenty of work to do though!</html:p>
                          </fr:mainmatter>
                        </fr:tree>
                        <fr:tree show-metadata="false" numbered="false">
                          <fr:frontmatter>
                            <fr:authors>
                              <fr:author>
                                <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                              </fr:author>
                            </fr:authors>
                            <fr:date>
                              <fr:year>2026</fr:year>
                              <fr:month>7</fr:month>
                              <fr:day>4</fr:day>
                            </fr:date>
                            <fr:title text="Parsing arithmetic expressions">Parsing arithmetic expressions</fr:title>
                          </fr:frontmatter>
                          <fr:mainmatter>
                            <html:p>There was some obvious low-hanging fruit to help improve some of the performance differences. The first was pre-parsing arithmetic expressions. <fr:link href="github.com/colis-anr/morbig" type="external">Morbig</fr:link>, the static parser, does not implement arithmetic expression parsing; so <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> does it for us. Before, it was on-demand, as we encountered an expression of the form <html:code>$((&lt;e&gt;))</html:code>. Now, it happens up front. This should improve tight-loops.</html:p>
                          </fr:mainmatter>
                        </fr:tree>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>7</fr:month>
                      <fr:day>4</fr:day>
                    </fr:date>
                    <fr:title text="Writing">Writing</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>More on this soon, but I have started writing two separate papers bringing all of this work together.</html:p>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>6</fr:month>
                  <fr:day>16</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w25/</fr:uri>
                <fr:display-uri>weekly-2026-w25</fr:display-uri>
                <fr:route>/weekly-2026-w25/</fr:route>
                <fr:title text="Three weeks in the Shell">Three weeks in the Shell</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>The last three weeks have been focused work on building <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>. In practice, I wanted to run the following two commands through <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>'s shell: <html:code>msh</html:code>:</html:p>
                <html:pre><![CDATA[apt-get install debootstrap -y
debootstrap stable /stable-chroot http://deb.debian.org/debian/]]></html:pre>
                <html:p>Seemingly innocuous. But both commands run <html:em>thousands</html:em> of lines of shell script. Whether that's <fr:link href="https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html" type="external">Debian package maintainer scripts</fr:link> or <fr:link href="https://salsa.debian.org/installer-team/debootstrap/-/blob/master/functions?ref_type=heads#L623" type="external">state-machines for processing package release files</fr:link>.</html:p>
                <html:p>What follows is a handful of the bugs and the shell scripts that caused them.</html:p>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>6</fr:month>
                      <fr:day>16</fr:day>
                    </fr:date>
                    <fr:title text="Field Splitting">Field Splitting</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>Edge-cases abound in the <fr:link href="/posixSpec/" title="IEEE/Open Group Standard for Information Technology--Portable Operating System Interface (POSIX™) Base Specifications, Issue 8" uri="https://patrick.sirref.org/posixSpec/" display-uri="posixSpec" type="local">POSIX shell specification</fr:link>; and field splitting is one of the most serious perpetrators. <html:em>Field splitting</html:em> happens after <html:em>word expansion</html:em>. It is a known footgun amongst shell script authors.</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="sh-source">$ count </html:span>
                        <html:span class="sh-punctuation-definition-subshell"><![CDATA[(]]></html:span>
                        <html:span class="sh-punctuation-definition-subshell"><![CDATA[)]]></html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-punctuation-definition-group"><![CDATA[{]]></html:span>
                        <html:span class="sh-meta-scope-group"> </html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-meta-scope-group"> </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">Count: </html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-special">#</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-group"> </html:span>
                        <html:span class="sh-punctuation-definition-group"><![CDATA[}]]></html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> FOO=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">hello world</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> count </html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">FOO</html:span>
                        <html:span class="sh-source">
</html:span>
                        <html:span class="sh-source">Count: 2
</html:span>
                      </html:code>
                    </html:pre>
                    <html:p>Quoting variables that need expanding will prevent field splitting from taking place, but it is an easy thing to forget.</html:p>
                    <html:p>Field splitting uses the <html:code>IFS</html:code> (internal field separator) environment variable to determine how to do the splitting, and it comes with some extra rules beyond "split on these characters".</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="sh-source">$ IFS=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double"> </html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> FOO=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double"> hello    world</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">FOO</html:span>
                        <html:span class="sh-source">
</html:span>
                        <html:span class="sh-source">hello world
</html:span>
                        <html:span class="sh-source">$ IFS=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double"> </html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> FOO=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double"> hello    world</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">FOO</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-source">
</html:span>
                        <html:span class="sh-source"> hello    world
</html:span>
                        <html:span class="sh-source">$ IFS=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">:</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> FOO=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">a:b:c:</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">FOO</html:span>
                        <html:span class="sh-source">
</html:span>
                        <html:span class="sh-source">a b c
</html:span>
                        <html:span class="sh-source">$ IFS=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">:</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> FOO=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">a:b::c:d</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">FOO</html:span>
                        <html:span class="sh-source">
</html:span>
                        <html:span class="sh-source">a b  c d
</html:span>
                        <html:span class="sh-source">$ IFS=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">:</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> FOO=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">:a:b:c</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">FOO</html:span>
                        <html:span class="sh-source">
</html:span>
                        <html:span class="sh-source"> a b c
</html:span>
                        <html:span class="sh-source">$ IFS=</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> FOO=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">unset   IFS</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">FOO</html:span>
                        <html:span class="sh-source">
</html:span>
                        <html:span class="sh-support-function-builtin">unset</html:span>
                        <html:span class="sh-source">   IFS
</html:span>
                      </html:code>
                    </html:pre>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>6</fr:month>
                          <fr:day>16</fr:day>
                        </fr:date>
                        <fr:title text="Variable assignment">Variable assignment</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>How a shell stores variables also impacts field splitting. Naively, <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> stored variables as strings, but really they should be stored as their expanded form.</html:p>
                        <html:p>Consider this example of saving a function output in a variable called <html:code>base</html:code>.</html:p>
                        <html:pre class="hilite">
                          <html:code>
                            <html:span class="sh-entity-name-function">iter</html:span>
                            <html:span class="sh-meta-function"> </html:span>
                            <html:span class="sh-punctuation-definition-arguments"><![CDATA[()]]></html:span>
                            <html:span class="sh-meta-function"> </html:span>
                            <html:span class="sh-punctuation-definition-group"><![CDATA[{]]></html:span>
                            <html:span class="sh-meta-scope-group">
</html:span>
                            <html:span class="sh-meta-scope-group"> </html:span>
                            <html:span class="sh-support-function-builtin">echo</html:span>
                            <html:span class="sh-meta-scope-group"> </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">a</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-meta-scope-group">
</html:span>
                            <html:span class="sh-meta-scope-group"> </html:span>
                            <html:span class="sh-support-function-builtin">echo</html:span>
                            <html:span class="sh-meta-scope-group"> </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">b</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-meta-scope-group">
</html:span>
                            <html:span class="sh-meta-scope-group"> </html:span>
                            <html:span class="sh-support-function-builtin">echo</html:span>
                            <html:span class="sh-meta-scope-group"> </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">c</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-meta-scope-group">
</html:span>
                            <html:span class="sh-punctuation-definition-group"><![CDATA[}]]></html:span>
                            <html:span class="sh-punctuation-definition-function">
</html:span>
                            <html:span class="sh-source">
</html:span>
                            <html:span class="sh-source">base=</html:span>
                            <html:span class="sh-punctuation-definition-string-begin"><![CDATA[$(]]></html:span>
                            <html:span class="sh-string-interpolated-dollar">iter</html:span>
                            <html:span class="sh-punctuation-definition-string-end"><![CDATA[)]]></html:span>
                            <html:span class="sh-source">
</html:span>
                            <html:span class="sh-source">
</html:span>
                            <html:span class="sh-keyword-control">for</html:span>
                            <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                            <html:span class="sh-variable-other-loop">n</html:span>
                            <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                            <html:span class="sh-keyword-control">in</html:span>
                            <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                            <html:span class="sh-punctuation-definition-variable">$</html:span>
                            <html:span class="sh-variable-other-normal">base</html:span>
                            <html:span class="sh-keyword-operator-list">;</html:span>
                            <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                            <html:span class="sh-keyword-control">do</html:span>
                            <html:span class="sh-meta-scope-for-in-loop">
</html:span>
                            <html:span class="sh-meta-scope-for-in-loop">  </html:span>
                            <html:span class="sh-support-function-builtin">echo</html:span>
                            <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">Got </html:span>
                            <html:span class="sh-punctuation-definition-variable">$</html:span>
                            <html:span class="sh-variable-other-normal">n</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-meta-scope-for-in-loop">
</html:span>
                            <html:span class="sh-keyword-control">done</html:span>
                            <html:span class="sh-source">
</html:span>
                            <html:span class="sh-source">
</html:span>
                            <html:span class="sh-keyword-control">for</html:span>
                            <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                            <html:span class="sh-variable-other-loop">n</html:span>
                            <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                            <html:span class="sh-keyword-control">in</html:span>
                            <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-punctuation-definition-variable">$</html:span>
                            <html:span class="sh-variable-other-normal">base</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-keyword-operator-list">;</html:span>
                            <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                            <html:span class="sh-keyword-control">do</html:span>
                            <html:span class="sh-meta-scope-for-in-loop">
</html:span>
                            <html:span class="sh-meta-scope-for-in-loop">  </html:span>
                            <html:span class="sh-support-function-builtin">echo</html:span>
                            <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">Got </html:span>
                            <html:span class="sh-punctuation-definition-variable">$</html:span>
                            <html:span class="sh-variable-other-normal">n</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-meta-scope-for-in-loop">
</html:span>
                            <html:span class="sh-keyword-control">done</html:span>
                            <html:span class="sh-source">
</html:span>
                          </html:code>
                        </html:pre>
                        <html:p>Later, when we come to iterate across the fields of <html:code>$base</html:code> we wouldn't be able to reconstruct the intial fields that <html:code>$(iter)</html:code> produced if we simply saved the result as a string. Too much information would have been lost.</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>6</fr:month>
                      <fr:day>16</fr:day>
                    </fr:date>
                    <fr:title text="Redirections">Redirections</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>Redirections control what file descriptors point to. Each redirection has a lifetime associated with it. For example: <html:code>cat &lt; README.md</html:code> really says "open <html:code>README.md</html:code> and execute the <html:code>cat</html:code> program mapping the process's FD <html:code>0</html:code> to the FD of <html:code>README.md</html:code>". When <html:code>cat</html:code> returns that redirection is no longer valid.</html:p>
                    <html:p>Other redirections are not so simple. Consider:</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="sh-source">$ </html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">!dlrow olleh</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-keyword-operator-redirect">&gt;</html:span>
                        <html:span class="sh-source"> input.txt
</html:span>
                        <html:span class="sh-source">$ </html:span>
                        <html:span class="sh-punctuation-definition-subshell"><![CDATA[(]]></html:span>
                        <html:span class="sh-meta-scope-subshell">read line</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-subshell"> </html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-meta-scope-subshell"> </html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">line</html:span>
                        <html:span class="sh-punctuation-definition-subshell"><![CDATA[)]]></html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-keyword-operator-redirect">&lt;</html:span>
                        <html:span class="sh-source"> input.txt </html:span>
                        <html:span class="sh-keyword-operator-pipe">|</html:span>
                        <html:span class="sh-source"> rev
</html:span>
                        <html:span class="sh-source">hello world</html:span>
                        <html:span class="sh-keyword-operator-pipe">!</html:span>
                        <html:span class="sh-source">
</html:span>
                      </html:code>
                    </html:pre>
                    <html:p>There are lots of redirections here too. Firstly the standard input for everything inside the braced command (<html:code>read line2; echo $line</html:code>) should be the open file descriptor for <html:code>input.txt</html:code>. The standard output of the braced command should be the writing end of a pipe whose reading end is passed to <html:code>rev</html:code> as that process's standard input. The final standard output for <html:code>rev</html:code> should be unchanged from what it was originally (the shell's standard output).</html:p>
                    <html:p>Getting redirections right is hard, consider this snippet from the debootstrap scripts (all in the context of stdout and stderr pointing to a <html:code>debootstrap.log</html:code> file and file descriptor <html:code>4</html:code> pointing at the true stdout).</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="sh-keyword-control">for</html:span>
                        <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                        <html:span class="sh-variable-other-loop">m</html:span>
                        <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                        <html:span class="sh-keyword-control">in</html:span>
                        <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">MIRRORS</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-for-in-loop"> </html:span>
                        <html:span class="sh-keyword-control">do</html:span>
                        <html:span class="sh-meta-scope-for-in-loop">
</html:span>
                        <html:span class="sh-meta-scope-for-in-loop">    pkgdest=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">TARGET</html:span>
                        <html:span class="sh-string-quoted-double">/</html:span>
                        <html:span class="sh-punctuation-definition-string-begin"><![CDATA[$(]]></html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">DLDEST</html:span>
                        <html:span class="sh-string-interpolated-dollar"> pkg </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">s</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-string-interpolated-dollar"> </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">c</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-string-interpolated-dollar"> </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">a</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-string-interpolated-dollar"> </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">m</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-string-interpolated-dollar"> </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">path</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-punctuation-definition-string-end"><![CDATA[)]]></html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-meta-scope-for-in-loop">
</html:span>
                        <html:span class="sh-meta-scope-if-block">    </html:span>
                        <html:span class="sh-keyword-control">if</html:span>
                        <html:span class="sh-meta-scope-if-block"><![CDATA[ []]></html:span>
                        <html:span class="sh-keyword-operator-pipe">!</html:span>
                        <html:span class="sh-meta-scope-if-block"> -e </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">pkgdest</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-meta-scope-if-block"><![CDATA[ ]]]></html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-if-block"> </html:span>
                        <html:span class="sh-keyword-control">then</html:span>
                        <html:span class="sh-meta-scope-if-block"> </html:span>
                        <html:span class="sh-keyword-control">continue</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-if-block"> </html:span>
                        <html:span class="sh-keyword-control">fi</html:span>
                        <html:span class="sh-meta-scope-for-in-loop">
</html:span>
                        <html:span class="sh-meta-scope-for-in-loop">    pkgs_to_get=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-string-begin"><![CDATA[$(]]></html:span>
                        <html:span class="sh-string-interpolated-dollar">download_debs </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">m</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-string-interpolated-dollar"> </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">pkgdest</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-string-interpolated-dollar"> </html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">pkgs_to_get</html:span>
                        <html:span class="sh-string-interpolated-dollar"> </html:span>
                        <html:span class="sh-keyword-operator-redirect">5&gt;&amp;1</html:span>
                        <html:span class="sh-string-interpolated-dollar"> </html:span>
                        <html:span class="sh-keyword-operator-redirect">1&gt;&amp;6</html:span>
                        <html:span class="sh-punctuation-definition-string-end"><![CDATA[)]]></html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-meta-scope-for-in-loop">
</html:span>
                        <html:span class="sh-meta-scope-if-block">    </html:span>
                        <html:span class="sh-keyword-control">if</html:span>
                        <html:span class="sh-meta-scope-if-block"><![CDATA[ [ -z]]></html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">pkgs_to_get</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-meta-scope-if-block"><![CDATA[ ]]]></html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-if-block"> </html:span>
                        <html:span class="sh-keyword-control">then</html:span>
                        <html:span class="sh-meta-scope-if-block"> </html:span>
                        <html:span class="sh-support-function-builtin">break</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-if-block"> </html:span>
                        <html:span class="sh-keyword-control">fi</html:span>
                        <html:span class="sh-meta-scope-for-in-loop">
</html:span>
                        <html:span class="sh-keyword-control">done</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-keyword-operator-redirect">6&gt;&amp;1</html:span>
                        <html:span class="sh-source">
</html:span>
                      </html:code>
                    </html:pre>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>6</fr:month>
                      <fr:day>16</fr:day>
                    </fr:date>
                    <fr:title text="More niche built-ins">More niche built-ins</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>As the dust begins to settle on the nuts and bolts of implementing a POSIX-ish shell, new challenges are allowed to emerge from deep within scripts.</html:p>
                    <html:p>One such challenge is the seemingly endless (I know, they're finite) shell built-ins! Whilst trying to run <fr:link href="https://wiki.debian.org/fakechroot" type="external">debootstrap using its <html:code>fakechroot</html:code> variant</fr:link>, I bumped into <html:code>getopts</html:code>... a whole command-line parsing shell built-in.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>6</fr:month>
                      <fr:day>16</fr:day>
                    </fr:date>
                    <fr:uri>https://patrick.sirref.org/eio_mem/</fr:uri>
                    <fr:display-uri>eio_mem</fr:display-uri>
                    <fr:route>/eio_mem/</fr:route>
                    <fr:title text="An In-memory Filesystem for Eio ">An In-memory Filesystem for Eio </fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>Towards the end of last week and the beginning of this week, I threw together a prototype for an <html:em>in-memory filesystem</html:em> for <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link>: <fr:link href="https://git.sirref.org/eio_mem" type="external">Eio_mem</fr:link>. Concretely, this provides a means for constructing an <html:code>Eio.Fs.dir_ty Eio.Path.t</html:code> value that is completely in-memory.</html:p>
                    <html:p>Thanks to <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link>'s capability-first approach, any existing code implemented against the <html:code>_ Eio.Path.t</html:code> API should just work with the in-memory filesystem.</html:p>
                    <html:p>There's a convenient <html:code>Eio_mem.Fs.load</html:code> function that can take a path from one filesystem (most likely a normal one from <html:code>Eio_linux</html:code> or <html:code>Eio_posix</html:code>) and replicate it into the in-memory filesystem. For example, you might have a static web-server pointing to a directory like <html:code>"/var/www"</html:code>. The following might be the only change you need to make it completely in-memory (<fr:link href="https://github.com/mirage/ocaml-cohttp/blob/main/cohttp-eio/examples/server2.ml" type="external">example from ocaml-cohttp</fr:link>):</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="diff-source"><![CDATA[ let () =]]></html:span>
                        <html:span class="diff-source">   let port = ref 8080 in
</html:span>
                        <html:span class="diff-source">   Arg.parse
</html:span>
                        <html:span class="diff-source"><![CDATA[     [ ("-p", Arg.Set_int port, " Listening port number(8080 by default)") ]]]></html:span>
                        <html:span class="diff-source">     ignore "An HTTP/1.1 server";
</html:span>
                        <html:span class="diff-source">   Eio_main.run @@ fun env -&gt;
</html:span>
                        <html:span class="diff-punctuation-definition-inserted">+</html:span>
                        <html:span class="diff-markup-inserted">  Eio_mem.run @@ fun menv -&gt;
</html:span>
                        <html:span class="diff-source">   Eio.Switch.run @@ fun sw -&gt;
</html:span>
                        <html:span class="diff-punctuation-definition-deleted">-</html:span>
                        <html:span class="diff-markup-deleted"><![CDATA[  (* Restrict to current directory: *)]]></html:span>
                        <html:span class="diff-source">
</html:span>
                        <html:span class="diff-punctuation-definition-deleted">-</html:span>
                        <html:span class="diff-markup-deleted">  let htdocs = Eio.Stdenv.cwd env in
</html:span>
                        <html:span class="diff-punctuation-definition-inserted">+</html:span>
                        <html:span class="diff-markup-inserted"><![CDATA[  (* Restrict to in-memory current directory: *)]]></html:span>
                        <html:span class="diff-source">
</html:span>
                        <html:span class="diff-punctuation-definition-inserted">+</html:span>
                        <html:span class="diff-markup-inserted"><![CDATA[  Eio_mem.Fs.load ~filter (Eio.Stdenv.cwd env);]]></html:span>
                        <html:span class="diff-source">
</html:span>
                        <html:span class="diff-punctuation-definition-inserted">+</html:span>
                        <html:span class="diff-markup-inserted">  let htdocs = Eio.Stdenv.cwd menv in
</html:span>
                        <html:span class="diff-source">   let socket =
</html:span>
                        <html:span class="diff-source">     Eio.Net.listen env#net ~sw ~backlog:128 ~reuse_addr:true
</html:span>
                        <html:span class="diff-source"><![CDATA[       (`Tcp (Eio.Net.Ipaddr.V4.loopback, !port))]]></html:span>
                      </html:code>
                    </html:pre>
                    <html:p>The keen-eyed reader might have noticed the <html:code>filter</html:code> parameter. This can be used to ignore certain paths (here I ignored <html:code>.git</html:code> and <html:code>_build</html:code> which were adding substantial time to loading the filesystem).</html:p>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>6</fr:month>
                  <fr:day>4</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w23/</fr:uri>
                <fr:display-uri>weekly-2026-w23</fr:display-uri>
                <fr:route>/weekly-2026-w23/</fr:route>
                <fr:title text="Using meio to debug Merry">Using meio to debug Merry</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>During a large rewrite of <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>, I introduced a regression causing certain shell programs to hang indefinitely. I thought it might be a good opportunity to try out <fr:link href="/meio/" title="Meio" uri="https://patrick.sirref.org/meio/" display-uri="meio" type="local">Meio</fr:link>.</html:p>
                <html:p>First, let's look at a trace of <fr:link href="/meio/" title="Meio" uri="https://patrick.sirref.org/meio/" display-uri="meio" type="local">Meio</fr:link> from some real-world programs. For example: <html:code>meio -- msh -c "sleep 100"</html:code>.</html:p>
                <html:img src="/bafkrmideu267e77ak6j5b7z2gda6nijipa7tfmz2r4ivsruktrhdzai3gi.gif" />
                <html:p>One problem that comes up immediately from this example is that the tree hierarchy of tasks and cancellation contexts makes it tricky to reason about the logging. Here, we can see the pipeline's cancellation context clearly labelled <html:code>pipeline-id1</html:code>. This is an <html:code>Eio.Switch.t</html:code> for the pipeline <html:code>sleep 100</html:code>. The actual execution log for <html:code>sleep 100</html:code> bubbles back up to the main fiber (that has no name, so it is called <html:code>&lt;task&gt;</html:code>). The daemon fiber, for ensuring the spawned process is reaped, is attached to the switch and is displayed below it.</html:p>
                <html:p><fr:link href="/meio/" title="Meio" uri="https://patrick.sirref.org/meio/" display-uri="meio" type="local">Meio</fr:link> has a long way to go. Most programs are too fast to be monitored in this way. Using something like <fr:link href="https://github.com/tokio-rs/console" type="external">tokio-console</fr:link>'s model might be a better idea here focusing less on the tree strucuture of the program, and more on the performance and monitoring of individual tasks. Without this, it turns out <fr:link href="/meio/" title="Meio" uri="https://patrick.sirref.org/meio/" display-uri="meio" type="local">Meio</fr:link> was not all that useful for debugging this specific issue (which ended up being some bad pipe management on my part).</html:p>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>6</fr:month>
                  <fr:day>1</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w22/</fr:uri>
                <fr:display-uri>weekly-2026-w22</fr:display-uri>
                <fr:route>/weekly-2026-w22/</fr:route>
                <fr:title text="Catching up in May">Catching up in May</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>I never quite found the time in May to write weeknotes. I think that's, in some ways, quite a good thing. My philosophy on weeknotes is not that they are a public punchcard of my progress. They are a tool for organising thoughts and keeping my colleagues abreast of what I am up to. Only the latter suggests any necessity for timeliness and given I frequently message, call and zulip them, if a few weeks fall between the cracks then that's okay!</html:p>
                <html:p>All that being said, I have tried to provide some thoughts and things I have been up to over the course of May and not just <html:em>week 22</html:em> of the year.</html:p>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>6</fr:month>
                      <fr:day>1</fr:day>
                    </fr:date>
                    <fr:title text="Tools for Shells">Tools for Shells</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>Some tools and libraries related to <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>.</html:p>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>6</fr:month>
                          <fr:day>1</fr:day>
                        </fr:date>
                        <fr:title text="Meio">Meio</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>The shell work continues, and as the complexity internally increases I have found myself wishing for some more tools for debugging concurrent <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> programs. Given enough pipes and compound commands, it is fairly easy to introduce a fair deal of concurrency to your shell programs. To this end I hope I have improved <fr:link href="/meio/" title="Meio" uri="https://patrick.sirref.org/meio/" display-uri="meio" type="local">meio</fr:link> somewhat <fr:link href="https://patrick.sirref.org/in this fork/" type="external">in this fork</fr:link>.</html:p>
                        <html:p>In particular, there were several things I wanted to improve.</html:p>
                        <html:ul>
                          <html:li>
                            <html:p>Simple UI improvements -- the set of tools for building TUI UIs from <fr:link href="https://github.com/let-def/lwd/tree/master/lib/nottui" type="external">nottui</fr:link> are, on closer inspection, rather good and simple. For example, turning a particular UI component into a scrollable pane is as simple <fr:link href="https://github.com/let-def/lwd/blob/818ce8aa4f892003b1f7fef3fac5eca5b68ce1e5/lib/nottui/nottui_widgets.mli#L41-L42" type="external">as wrapping it in a call to this function</fr:link>.</html:p>
                          </html:li>
                          <html:li>
                            <html:p>Logging more runtime events -- <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> programs emit information via OCaml's runtime events. This is how <fr:link href="/meio/" title="Meio" uri="https://patrick.sirref.org/meio/" display-uri="meio" type="local">meio</fr:link> knows which fiber is currently active for example. It also emits information like "the current fiber created a new promise" and "this fiber is blocked waiting for a promise". I have not figured out an intuitive way for representing this in <fr:link href="/meio/" title="Meio" uri="https://patrick.sirref.org/meio/" display-uri="meio" type="local">meio</fr:link>'s tree-view, but I did turn these into "system logs" that appear in the per-fiber logs which is already an improvement. Thanks to <fr:link href="/talex5/" title="Thomas Leonard" uri="https://patrick.sirref.org/talex5/" display-uri="talex5" type="local">Thomas</fr:link> for helping understand the runtime event sequence for logging this information.</html:p>
                          </html:li>
                          <html:li>
                            <html:p>Timestamping where possible -- all runtime events come with a timestamp, these were mostly discarded by <fr:link href="/meio/" title="Meio" uri="https://patrick.sirref.org/meio/" display-uri="meio" type="local">meio</fr:link>.</html:p>
                          </html:li>
                        </html:ul>
                        <html:img src="/bafkrmial3mishe2iyi2qysvvcmcpbesybiqsym5gklnsjzvrxdrgff2jk4.gif" />
                        <html:p>Some aspects of <fr:link href="/meio/" title="Meio" uri="https://patrick.sirref.org/meio/" display-uri="meio" type="local">meio</fr:link> made it clear that labelling <html:code>Eio.Switch</html:code>es and <html:code>Eio.Promise</html:code>s might be a good practice. After <fr:link href="https://digitalflapjack.com/weeknotes/ocaml-point-clouds/" type="external">reading Michael's Eio forays</fr:link>, I can't help but wonder if a more opinionated <html:em>Eio for Systems Programmers</html:em> guide might be useful. I think the Eio README does a wonderful job of introducing most of the low-level concepts, but not quite how they are used by the working systems programmer.</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>6</fr:month>
                          <fr:day>1</fr:day>
                        </fr:date>
                        <fr:title text="Glob">Glob</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>A little dismayed at the lack of progress I was making with <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>, I switched to a specific problem I knew I wanted to fix -- removing the dependency on <html:code>glob(3)</html:code>, replacing it with a <fr:link href="https://git.sirref.org/glob/" type="external">pure OCaml implementation</fr:link>.</html:p>
                        <html:pre class="hilite">
                          <html:code>
                            <html:span class="ocaml-keyword">let</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-entity-name-function-binding">get_txts</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">fs</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">=</html:span>
                            <html:span class="ocaml-source">
</html:span>
                            <html:span class="ocaml-source">  </html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Glob_eio</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-source">glob</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">fs</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Glob</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                            <html:span class="ocaml-source">of_component</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source"><![CDATA[[]]></html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">dir_sep</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-support-type">string</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-string-quoted-double">home</html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">dir_sep</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">star</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-support-type">string</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-string-quoted-double">.txt</html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source"><![CDATA[]]]></html:span>
                            <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                            <html:span class="ocaml-source">
</html:span>
                          </html:code>
                        </html:pre>
                        <html:p><fr:link href="https://tangled.org/patrick.sirref.org/glob/pulls/1/round/0" type="external">Anil provided some issues with fixes too</fr:link> which was very appreciated.</html:p>
                        <html:p>Beyond factoring out <html:code>fork+exec</html:code>, <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> should now be portable to the browser. Of course there are lots of other refactorings needed to (e.g. a fake-filesystem) -- but I'm interested in how I can use Eio's filesystem capabilities to do this pretty seemlessly. Having an emeddable, pure OCaml shell may also unlock some cool <fr:link href="https://bcantrill.dtrace.org/2016/01/22/unikernels-are-unfit-for-production/" type="external">debugging capabilities for unikernels</fr:link> too!</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>6</fr:month>
                      <fr:day>1</fr:day>
                    </fr:date>
                    <fr:title text="Geocaml">Geocaml</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p><fr:link href="/mdales/" title="Michael W. Dales" uri="https://patrick.sirref.org/mdales/" display-uri="mdales" type="local">Michael</fr:link> has been valiantly trying some of the <fr:link href="/geocaml/" title="Geocaml" uri="https://patrick.sirref.org/geocaml/" display-uri="geocaml" type="local">Geocaml</fr:link> libraries in <fr:link href="https://digitalflapjack.com/weeknotes/geocaml-hacking/" type="external">his work on rendering LiDAR point clouds</fr:link>. Where possible, I have been reviewing Michael's PRs and helping implement features he might find useful. I also took his <fr:link href="https://github.com/mdales/laserver" type="external">Laserver</fr:link> for a spin, and suggested <fr:link href="https://github.com/mdales/laserver/pull/1" type="external">a few</fr:link> <fr:link href="https://github.com/mdales/laserver/pull/2" type="external">changes</fr:link>.</html:p>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>6</fr:month>
                          <fr:day>1</fr:day>
                        </fr:date>
                        <fr:title text="Rtree">Rtree</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>After a <fr:link href="https://github.com/ocaml/opam-repository/pull/29955" type="external">comical series of attempts</fr:link>, I (or rather <fr:link href="/anilmadhavapeddy/" title="Anil Madhavapeddy" uri="https://patrick.sirref.org/anilmadhavapeddy/" display-uri="anilmadhavapeddy" type="local">Anil</fr:link>) released <html:code>rtree.0.2.0</html:code> which contained:</html:p>
                        <html:ul>
                          <html:li>
                            <html:p>A <fr:link href="https://github.com/geocaml/ocaml-rtree/pull/39" type="external">bug fix</fr:link> where not all the tree values were being returned.</html:p>
                          </html:li>
                          <html:li>
                            <html:p>A new <fr:link href="https://github.com/geocaml/ocaml-rtree/pull/43" type="external">bounds function</fr:link> for getting an Rtree's maximum bound.</html:p>
                          </html:li>
                          <html:li>
                            <html:p>A set of <fr:link href="https://github.com/geocaml/ocaml-rtree/pull/44" type="external">simple removal functions</fr:link>.</html:p>
                          </html:li>
                        </html:ul>
                      </fr:mainmatter>
                    </fr:tree>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>6</fr:month>
                          <fr:day>1</fr:day>
                        </fr:date>
                        <fr:title text="PROJ">PROJ</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p><fr:link href="/mdales/" title="Michael W. Dales" uri="https://patrick.sirref.org/mdales/" display-uri="mdales" type="local">Michael</fr:link> added some new functions to <fr:link href="https://github.com/geocaml/ocaml-proj" type="external">ocaml-proj</fr:link>. Previously, I had worked on making PROJ more portable <fr:link href="/ocaml-proj-virt/" title="Forking in Shells &amp; Library Maintenance › Browser support for ocaml-proj " uri="https://patrick.sirref.org/ocaml-proj-virt/" display-uri="ocaml-proj-virt" type="local">by using virtual libraries</fr:link>. This was a little restrictive, as it forced users to <html:em>always</html:em> implement a JS version of any functionality that exists in the native version of PROJ.</html:p>
                        <html:p>To make it easier to contribute (and allow the C bindings to contain more specific bindings) I carved out the <fr:link href="https://github.com/geocaml/ocaml-proj/pull/1" type="external">two virtual library implementations into standalone libraries, and reimplemented the virtual library support using these libraries</fr:link>. Now projects can depend directly on <html:code>proj_c</html:code> if they wish to have more features in exchange for being less portable.</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>6</fr:month>
                      <fr:day>1</fr:day>
                    </fr:date>
                    <fr:title text="Ppxlib">Ppxlib</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>Thankfully, work on <fr:link href="/ppxlib/" title="Ppxlib" uri="https://patrick.sirref.org/ppxlib/" display-uri="ppxlib" type="local">ppxlib</fr:link> has been <fr:link href="https://github.com/ocaml-ppx/ppxlib/pull/638" type="external">relatively</fr:link> <fr:link href="https://github.com/ocaml-ppx/ppxlib/pull/640" type="external">quiet</fr:link>. <fr:link href="/nathanreb/" title="Nathan Rebours" uri="https://patrick.sirref.org/nathanreb/" display-uri="nathanreb" type="local">Nathan</fr:link> also opened a PR for <fr:link href="https://github.com/ocaml-ppx/ppxlib/pull/642" type="external">initial OCaml 5.6 support</fr:link>. Keeping on top of 5.6 features is probably the right way to go about this (rather than one giant PR during the 5.6 release cycle).</html:p>
                    <html:p>I'm particularly happy to have some better <fr:link href="https://github.com/ocaml-ppx/ppxlib/pull/640" type="external">migration error handling</fr:link>. The previous errors, though uncommon, were confusing ppx users and hopefully this will help ppx authors and ppxlib maintainers triaging important encoding bugs or feature wishes.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>6</fr:month>
                      <fr:day>1</fr:day>
                    </fr:date>
                    <fr:title text="Self-hosting">Self-hosting</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>I now have a fairly workable setup for self-hosting source code with <fr:link href="https://git.sirref.org/" type="external">git.sirref.org</fr:link> and <fr:link href="https://tests.sirref.org/" type="external">tests.sirref.org</fr:link>. One piece that is missing, is a publishing workflow. I spent a little time building a <fr:link href="https://yocaml.github.io/" type="external">YOCaml</fr:link> static-site generator for packaging OCaml libraries. Unfortunately, this was full of rabbit holes and busy work which was frustrating.</html:p>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>6</fr:month>
                          <fr:day>1</fr:day>
                        </fr:date>
                        <fr:title text="Tar-eio">Tar-eio</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>One such rabbit hole was <fr:link href="https://ocaml.org/p/tar-eio" type="external">tar-eio</fr:link>. It was in a bit of a sorry state, so I took the opportunity to improve the API for new users. Tarring a directory is now as simple as:</html:p>
                        <html:pre class="hilite">
                          <html:code>
                            <html:span class="ocaml-keyword">let</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-entity-name-function-binding">foo</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">=</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Path</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                            <html:span class="ocaml-source">cwd</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">/</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-string-quoted-double">foo</html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-other">in</html:span>
                            <html:span class="ocaml-source">
</html:span>
                            <html:span class="ocaml-keyword">let</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-entity-name-function-binding">foo_tar</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">=</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Path</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                            <html:span class="ocaml-source">cwd</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">/</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-string-quoted-double">foo.tar</html:span>
                            <html:span class="ocaml-string-quoted-double">"</html:span>
                            <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-other">in</html:span>
                            <html:span class="ocaml-source">
</html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Path</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-source">with_open_out</html:span>
                            <html:span class="ocaml-source"> ~</html:span>
                            <html:span class="ocaml-source">create</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                            <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                            <html:span class="ocaml-constant-language-polymorphic-variant">`Or_truncate</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-constant-numeric-octal-integer">0o755</html:span>
                            <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">foo_tar</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">@@</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-other">fun</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">foo_tar</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                            <html:span class="ocaml-source">
</html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Switch</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-source">run</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">@@</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-other">fun</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">sw</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                            <html:span class="ocaml-source">
</html:span>
                            <html:span class="ocaml-keyword">let</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-entity-name-function-binding">t</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-operator">=</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Tar_eio</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-source">create</html:span>
                            <html:span class="ocaml-source"> ~</html:span>
                            <html:span class="ocaml-source">sw</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">foo</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-keyword-other">in</html:span>
                            <html:span class="ocaml-source">
</html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Tar_eio</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-source">run</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">t</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                            <html:span class="ocaml-constant-language-capital-identifier">Tar_eio</html:span>
                            <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                            <html:span class="ocaml-source">flow_of_file</html:span>
                            <html:span class="ocaml-source"> </html:span>
                            <html:span class="ocaml-source">tar</html:span>
                            <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                            <html:span class="ocaml-source">
</html:span>
                          </html:code>
                        </html:pre>
                        <html:p>There was also a series of security issues that the <html:code>tar</html:code> library had that were fixed too, you can read about those in <fr:link href="/talex5/" title="Thomas Leonard" uri="https://patrick.sirref.org/talex5/" display-uri="talex5" type="local">Thomas's</fr:link> <fr:link href="https://notes.roscidus.com/2026/05/24/" type="external">week</fr:link><fr:link href="https://notes.roscidus.com/2026/06/01/" type="external">notes</fr:link>.</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>4</fr:month>
                  <fr:day>28</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w17/</fr:uri>
                <fr:display-uri>weekly-2026-w17</fr:display-uri>
                <fr:route>/weekly-2026-w17/</fr:route>
                <fr:title text="Shell forks revisited">Shell forks revisited</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>Whilst I did get up to some other work last week, I decided to dedicate this weekly to <html:code>fork</html:code>-ing in the shell.</html:p>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>4</fr:month>
                      <fr:day>28</fr:day>
                    </fr:date>
                    <fr:title text="Must we fork?">Must we fork?</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>I have spent some time trying to figure out: can you implement a shell that never has to <html:code>fork</html:code> and run its own code (as opposed to <html:code>fork+exec</html:code>-ing, which is necessary)? There are, at least, two bits of POSIX shell semantics that make this hard: concurrency and redirects, particularly in the presence of shell built-ins.</html:p>
                    <html:p>Individual commands in a pipeline, according to the <fr:link href="/posixSpec/" title="IEEE/Open Group Standard for Information Technology--Portable Operating System Interface (POSIX™) Base Specifications, Issue 8" uri="https://patrick.sirref.org/posixSpec/" display-uri="posixSpec" type="local">POSIX specification</fr:link>, will have their standard input and output connected up before other redirections.</html:p>
                    <html:blockquote>
                      <html:p>The standard input, standard output, or both of a command shall be considered to be assigned by the pipeline before any redirection specified by redirection operators that are part of the command.</html:p>
                    </html:blockquote>
                    <html:p>Additionally:</html:p>
                    <html:blockquote>
                      <html:p>...each command of a multi-command pipeline is in a subshell environment; as an extension, however, any or all commands in a pipeline may be executed in the current environment.</html:p>
                    </html:blockquote>
                    <html:p>Where a subshell is defined as:</html:p>
                    <html:blockquote>
                      <html:p>Subshell: A shell execution environment, distinguished from the main or current shell execution environment.</html:p>
                    </html:blockquote>
                    <html:p>We will note that subshell <fr:tex display="inline"><![CDATA[\neq ]]></fr:tex> child process. In practice, however, the two are used fairly interchangeably given the semantics of spawning a child process. Perhaps the two most crucial aspects of a given command in a subshell are that:</html:p>
                    <html:ol>
                      <html:li>
                        <html:p>It is run <html:em>in parallel</html:em> with the other commands.</html:p>
                      </html:li>
                      <html:li>
                        <html:p>It should be <fr:link href="https://en.wikipedia.org/wiki/Reentrancy_(computing)" type="external"><html:em>reentrant</html:em></fr:link>.</html:p>
                      </html:li>
                    </html:ol>
                    <html:p>In what follows we will assume that we are discussing a multi-command pipeline <fr:tex display="inline"><![CDATA[p]]></fr:tex>.</html:p>
                    <html:p>
                      <fr:tex display="block"><![CDATA[ \texttt {p} =  \texttt {cmd}_1 | ... | \texttt {cmd}_n \text { where } n \geq  2 ]]></fr:tex>
                    </html:p>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>4</fr:month>
                          <fr:day>28</fr:day>
                        </fr:date>
                        <fr:title text="Pipeline parallelism">Pipeline parallelism</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>The <fr:link href="/posixSpec/" title="IEEE/Open Group Standard for Information Technology--Portable Operating System Interface (POSIX™) Base Specifications, Issue 8" uri="https://patrick.sirref.org/posixSpec/" display-uri="posixSpec" type="local">POSIX specification</fr:link> does not mandate parallelism explicitly for commands in a pipeline. However, in practice the semantics of a pipeline necessitate concurrency at the very least.</html:p>
                        <html:p>Suppose we have <fr:tex display="inline"><![CDATA[\texttt {cmd}_1 | \texttt {cmd}_2]]></fr:tex> where <fr:tex display="inline"><![CDATA[\texttt {cmd}_1]]></fr:tex> produces an infinite amount of output (e.g., <html:code>cat /dev/urandom</html:code>) and <fr:tex display="inline"><![CDATA[\texttt {cmd}_2]]></fr:tex> consumes a finite amount of input (e.g., <fr:link href="https://github.com/dinosaure/hxd" type="external"><html:code>hxd-xxd -l100</html:code></fr:link>). What is the result of running this pipeline with the default POSIX shell options?</html:p>
                        <html:pre class="hilite">
                          <html:code>
                            <html:span class="sh-source">$ dash -c </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">cat /dev/urandom | hxd.xxd -l100</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-source">
</html:span>
                            <html:span class="sh-source">00000000: 5033 7981 ae44 d475 074c 5361 0a9e 3828  P3y..D.u.LSa..8</html:span>
                            <html:span class="sh-punctuation-definition-subshell"><![CDATA[(]]></html:span>
                            <html:span class="sh-meta-scope-subshell">
</html:span>
                            <html:span class="sh-meta-scope-subshell">00000010: f88c 1f9e 3b0e bcde 528a f78c b1e4 7195  ....</html:span>
                            <html:span class="sh-keyword-operator-list">;</html:span>
                            <html:span class="sh-meta-scope-subshell">...R.....q.
</html:span>
                            <html:span class="sh-meta-scope-subshell"><![CDATA[00000020: 16f2 8325 c90b 9310 539c af04 8289 c3a0  ...%....S.......]]></html:span>
                            <html:span class="sh-meta-scope-subshell">
</html:span>
                            <html:span class="sh-meta-scope-subshell">00000030: 57c5 680a 2c81 a999 71b6 708e 95df e720  W.h.,...q.p....
</html:span>
                            <html:span class="sh-meta-scope-subshell">00000040: 004c 407a 8365 9448 65c6 b435 bed2 4084  .L@z.e.He..5..@.
</html:span>
                            <html:span class="sh-meta-scope-subshell">00000050: 62ea 96ab 2b23 0385 f9d2 fc30 d090 1719  b...+#.....0....
</html:span>
                            <html:span class="sh-meta-scope-subshell">00000060: 68d1 c514                                h...
</html:span>
                            <html:span class="sh-meta-scope-subshell">cat: write error: Broken pipe
</html:span>
                          </html:code>
                        </html:pre>
                        <html:p>There are three factors that influence the pipeline's execution behaviour (i.e. what is done about the commands that are run and the exit status we return).</html:p>
                        <html:ol>
                          <html:li>
                            <html:p><html:code>pipefail</html:code>: With <html:code>pipefail</html:code> set to <html:code>false</html:code> (the default), the exit status of the pipeline is entirely decided upon by the exit status of the <html:em>rightmost</html:em> command (above <fr:tex display="inline"><![CDATA[\texttt {cmd}_2]]></fr:tex>). With <html:code>pipefail</html:code>, the pipe will exit with 0 if all commands exit with 0; otherwise the non-zero exit status of the <html:em>rightmost</html:em> command is taken.</html:p>
                          </html:li>
                          <html:li>
                            <html:p><html:code>!</html:code>: If the pipeline begins with <html:code>!</html:code> then this inverts the exit code in the way you might expect.</html:p>
                          </html:li>
                          <html:li>
                            <html:p>All commands must terminate.</html:p>
                          </html:li>
                        </html:ol>
                        <html:p>Whilst in theory <fr:tex display="inline"><![CDATA[\texttt {cmd}_1]]></fr:tex> does not terminate, the termination of <fr:tex display="inline"><![CDATA[\texttt {cmd}_2]]></fr:tex> closes the reading end of the pipe resulting in the <html:code>Broken pipe</html:code> system error.</html:p>
                        <html:p>Let's replace <fr:tex display="inline"><![CDATA[\texttt {cmd}_1]]></fr:tex> with the following <html:em>compound command</html:em> that also produces, left to its own devices, infinite output on <html:code>stdout</html:code>.</html:p>
                        <html:pre class="hilite">
                          <html:code>
                            <html:span class="sh-keyword-control">while</html:span>
                            <html:span class="sh-meta-scope-while-loop"> </html:span>
                            <html:span class="sh-support-function-builtin">echo</html:span>
                            <html:span class="sh-meta-scope-while-loop"> </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">some bytes</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-keyword-operator-list">;</html:span>
                            <html:span class="sh-meta-scope-while-loop"> </html:span>
                            <html:span class="sh-keyword-control">do</html:span>
                            <html:span class="sh-meta-scope-while-loop">
</html:span>
                            <html:span class="sh-meta-scope-while-loop">  </html:span>
                            <html:span class="sh-support-function-builtin">:</html:span>
                            <html:span class="sh-meta-scope-while-loop">
</html:span>
                            <html:span class="sh-keyword-control">done</html:span>
                            <html:span class="sh-source"> </html:span>
                            <html:span class="sh-keyword-operator-pipe">|</html:span>
                            <html:span class="sh-source"> hxd.xxd -l100
</html:span>
                          </html:code>
                        </html:pre>
                        <html:p>In this example, we no longer have the luxury of being able to <html:code>fork</html:code> and <html:code>exec</html:code>. The compound command (the <html:code>while</html:code> loop) forces our hand -- we must rely on the internal execution logic of the shell to keep it going. How then do we prevent <fr:tex display="inline"><![CDATA[\texttt {cmd}_1]]></fr:tex> from blocking our shell from spawning <fr:tex display="inline"><![CDATA[\texttt {cmd}_2]]></fr:tex>?</html:p>
                        <html:p>For most shells, this is where they might <html:code>fork</html:code> themselves and allow the <html:code>while</html:code> loop to execute in the child. The parent continuing on. Alternatively, the work could continue on in a separate thread (in OCaml parlance, domain). Fibers (green threads, coroutines etc.) are only possible if there are a sufficient number of co-operative yields (which can be hard to track: any control flow or shell built-in would need to ensure they periodically yield to the other commands in the pipeline).</html:p>
                        <html:p>With that being said, it would seem we have workarounds to ensure the parallel aspects of pipelines are possible to build without forking the shell state itself.</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>4</fr:month>
                          <fr:day>28</fr:day>
                        </fr:date>
                        <fr:title text="Command Reentrancy">Command Reentrancy</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>Command reentrancy deals with the fact that commands in a pipeline run in <html:em>subshells</html:em>. In practice, this means any <fr:tex display="inline"><![CDATA[\texttt {cmd}_i]]></fr:tex> should not impact the <html:em>execution environment</html:em> of any other <fr:tex display="inline"><![CDATA[\texttt {cmd}_j]]></fr:tex>. Commands may certainly interfere with one another: a rogue <html:code>rm</html:code> might remove a file that some other command was about to read or was in the middle of reading!</html:p>
                        <html:p><fr:tex display="inline"><![CDATA[\S 2.13]]></fr:tex> of the POSIX shell specification describes the execution environment. In <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> we lift this into the <html:code>ctx</html:code> type making it completely immutable; the only way to update the context is to destructively declare new fields, e.g., <html:code><![CDATA[{ ctx with cwd = "/home/bactrian" }]]></html:code>. All shell built-ins work directly from the context as opposed to any state that might <html:em>also</html:em> be kept by the process itself (e.g., we do not call <html:code>getcwd(3)</html:code> to expand <html:code>PWD</html:code>, but instead read it from the context).</html:p>
                        <html:p>Most state is handled nicely by this <html:em>functional</html:em> implementation of the shell; except open (or not) file descriptors. The <html:code>exec</html:code> built-in is particularly thorny in this regard:</html:p>
                        <html:blockquote>
                          <html:p>If exec is specified with no operands, any redirections associated with the exec command shall be made in the current shell execution environment.</html:p>
                        </html:blockquote>
                        <html:p>So, <html:code>exec 3&gt;&amp;1</html:code> says "in the current execution environment map file descriptor <html:code>3</html:code> to <html:code>stdout</html:code>" and <html:code>exec &gt; /dev/null</html:code> says "in the current execution environment map <html:code>stdout</html:code> to <html:code>/dev/null</html:code>. This is often used to silence anything writing to <html:code>stdout</html:code>.</html:p>
                        <html:pre class="hilite">
                          <html:code>
                            <html:span class="sh-entity-name-function">info</html:span>
                            <html:span class="sh-meta-function"> </html:span>
                            <html:span class="sh-punctuation-definition-arguments"><![CDATA[()]]></html:span>
                            <html:span class="sh-meta-function"> </html:span>
                            <html:span class="sh-punctuation-definition-group"><![CDATA[{]]></html:span>
                            <html:span class="sh-meta-scope-group">
</html:span>
                            <html:span class="sh-meta-scope-group">  </html:span>
                            <html:span class="sh-support-function-builtin">echo</html:span>
                            <html:span class="sh-meta-scope-group"> </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">INFO: </html:span>
                            <html:span class="sh-punctuation-definition-variable">$</html:span>
                            <html:span class="sh-variable-other-positional">1</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-meta-scope-group"> </html:span>
                            <html:span class="sh-keyword-operator-redirect">&gt;&amp;3</html:span>
                            <html:span class="sh-meta-scope-group">
</html:span>
                            <html:span class="sh-punctuation-definition-group"><![CDATA[}]]></html:span>
                            <html:span class="sh-punctuation-definition-function">
</html:span>
                            <html:span class="sh-source">
</html:span>
                            <html:span class="sh-support-function-builtin">exec</html:span>
                            <html:span class="sh-source"> </html:span>
                            <html:span class="sh-keyword-operator-redirect">3&gt;&amp;1</html:span>
                            <html:span class="sh-source"> </html:span>
                            <html:span class="sh-keyword-operator-redirect">&gt;</html:span>
                            <html:span class="sh-source"> /dev/null
</html:span>
                            <html:span class="sh-source">info </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">Starting up server...</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-source">
</html:span>
                            <html:span class="sh-support-function-builtin">echo</html:span>
                            <html:span class="sh-source"> </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">Some noisy program spamming STDOUT</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-source">
</html:span>
                            <html:span class="sh-source">info </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">All is quiet...</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-source">
</html:span>
                          </html:code>
                        </html:pre>
                        <html:p>If we are not running shell built-ins inside new child processes, then we must lift the file descriptor table into the context as well and <html:em>disallow</html:em> any two built-ins to run in parallel. Consider this example:</html:p>
                        <html:pre class="hilite">
                          <html:code>
                            <html:span class="sh-support-function-builtin">exec</html:span>
                            <html:span class="sh-source"> </html:span>
                            <html:span class="sh-keyword-operator-redirect">&lt;</html:span>
                            <html:span class="sh-source"> ./hello.txt
</html:span>
                            <html:span class="sh-source">
</html:span>
                            <html:span class="sh-keyword-control">while</html:span>
                            <html:span class="sh-meta-scope-while-loop"> </html:span>
                            <html:span class="sh-support-function-builtin">read</html:span>
                            <html:span class="sh-meta-scope-while-loop"> line</html:span>
                            <html:span class="sh-keyword-operator-list">;</html:span>
                            <html:span class="sh-meta-scope-while-loop"> </html:span>
                            <html:span class="sh-keyword-control">do</html:span>
                            <html:span class="sh-meta-scope-while-loop">
</html:span>
                            <html:span class="sh-meta-scope-while-loop">  </html:span>
                            <html:span class="sh-support-function-builtin">echo</html:span>
                            <html:span class="sh-meta-scope-while-loop"> </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">Got </html:span>
                            <html:span class="sh-punctuation-definition-variable">$</html:span>
                            <html:span class="sh-variable-other-normal">line</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-meta-scope-while-loop"> </html:span>
                            <html:span class="sh-keyword-operator-redirect">&gt;&amp;2</html:span>
                            <html:span class="sh-meta-scope-while-loop">
</html:span>
                            <html:span class="sh-keyword-control">done</html:span>
                            <html:span class="sh-source"> </html:span>
                            <html:span class="sh-keyword-operator-pipe">|</html:span>
                            <html:span class="sh-source"> </html:span>
                            <html:span class="sh-punctuation-definition-subshell"><![CDATA[(]]></html:span>
                            <html:span class="sh-meta-scope-subshell">exec </html:span>
                            <html:span class="sh-keyword-operator-redirect">&lt;</html:span>
                            <html:span class="sh-meta-scope-subshell"> ./world.txt</html:span>
                            <html:span class="sh-keyword-operator-list">;</html:span>
                            <html:span class="sh-meta-scope-subshell"> cat</html:span>
                            <html:span class="sh-punctuation-definition-subshell"><![CDATA[)]]></html:span>
                            <html:span class="sh-source">
</html:span>
                          </html:code>
                        </html:pre>
                        <html:p>If we allow <html:code>exec</html:code> to <html:em>change</html:em> the set of open file descriptors of the shell process directly, then individual commands in the pipeline are no longer re-entrant. The pipeline here is not being used to pipe bytes from one command to another, but instead to expose concurrency and the subshell behaviour.</html:p>
                        <html:p>The second <html:code>exec &lt; ./world.txt</html:code> has no impact on the currently opened file on <html:code>stdin</html:code>, the while loop continues to read <html:code>./hello.txt</html:code>. This is straightforward to implement with <html:code>fork</html:code> as you can <html:code>dup2</html:code> any files you need. The bookkeeping and isolation is all provided by the POSIX process semantics. Without <html:code>fork</html:code>, the file descriptors would have to be saved and restored on every suspension and resumption to provide an illusion of isolation.</html:p>
                        <html:p>We could relax our rule to "disallow any two built-ins to run in parallel" by taking a lock on the file descriptor table, but considering the lock is held from the point of resumption until the next suspension it might not achieve very much in practice.</html:p>
                        <html:p>Some of the intricacies of scheduling are also covered in <fr:link href="/greenberg2020smoosh/" title="Executable formal semantics for the POSIX shell" uri="https://patrick.sirref.org/greenberg2020smoosh/" display-uri="greenberg2020smoosh" type="local">Michael Greenberg's <html:em>An executable and formal semantics of the POSIX shell</html:em></fr:link>.</html:p>
                        <html:blockquote>
                          <html:p>To motivate the question, consider the following two pipelines:</html:p>
                          <html:p>(1) <html:code>while true; do echo 5; done | true</html:code></html:p>
                          <html:p>(2) <html:code><![CDATA[while true; do echo 5; done | { read x; echo $((x+42)); }]]></html:code></html:p>
                          <html:p>Both pipelines spawn two processes, both of which use shell builtins exclusively: neither of these pipelines needs to make an <html:code>execve</html:code> system call (though some systems may implement true or echo as executables, Smoosh and most shells build them in).</html:p>
                        </html:blockquote>
                        <html:p>Scheduling is an opaque part of the <fr:link href="/posixSpec/" title="IEEE/Open Group Standard for Information Technology--Portable Operating System Interface (POSIX™) Base Specifications, Issue 8" uri="https://patrick.sirref.org/posixSpec/" display-uri="posixSpec" type="local">POSIX specification</fr:link>. Indeed, as we have argued here, there is <html:em>no</html:em> semantics for scheduling. Here is a similar pipeline run through a script that <html:code>strace</html:code>s for <html:code>clone</html:code> and <html:code>fork</html:code> calls on various shells:</html:p>
                        <html:pre><![CDATA[% ./test/check_fork.sh "while echo 5; do : ; done | true"
bash  3
dash  3
yash  1 
zsh   1
ksh   1
ash   3]]></html:pre>
                        <html:p>It might make an interesting use case for effects in OCaml -- whilst we can certainly piggyback on <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link>'s existing suspension mechanisms, this is slightly different in that we need to ensure some preconditions are true before resuming certain computations. Fiber-local state is insufficient as programs implicitly depend on global state of the process (in particular the FD table). In this sense, we need some form of suspension-resumption hooks.</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>4</fr:month>
                  <fr:day>18</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w16/</fr:uri>
                <fr:display-uri>weekly-2026-w16</fr:display-uri>
                <fr:route>/weekly-2026-w16/</fr:route>
                <fr:title text="Landlocked Eio">Landlocked Eio</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>I spent the start of last week wrapping up the "local" CI I mentioned <fr:link href="/weekly-2026-w15/" title="Moving away from Github" uri="https://patrick.sirref.org/weekly-2026-w15/" display-uri="weekly-2026-w15" type="local">the previous week</fr:link>. Unfortunately it was mostly a packaging nightmare, followed by missing features in <html:code>ocaml-ci-local</html:code>. It would be great to have a richer CLI tool for running tests on a directory of git repositories, but unfortunately most of the existing tooling has prioritised the Github/Gitlab etc. APIs.</html:p>
                <html:p>After coming across <fr:link href="https://minimal.dev/" type="external">minimal</fr:link> during the week, I took it as an opportunity to learn a little more about Linux's <fr:link href="https://lwn.net/Articles/859908/" type="external">Landlock</fr:link> mechanism.</html:p>
                <fr:tree show-metadata="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>4</fr:month>
                      <fr:day>18</fr:day>
                    </fr:date>
                    <fr:uri>https://patrick.sirref.org/landlocked-eio/</fr:uri>
                    <fr:display-uri>landlocked-eio</fr:display-uri>
                    <fr:route>/landlocked-eio/</fr:route>
                    <fr:title text="Landlocked Eio ">Landlocked Eio </fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p><fr:link href="https://lwn.net/Articles/859908/" type="external">Landlock</fr:link> is a sandboxing mechanism that has existed in the Linux kernel since 5.13. It allows unprivileged processes a means to restrict their ambient rights (e.g. creating a file or opening a network connection). These rights are ambient in the <fr:link href="https://en.wikipedia.org/wiki/Ambient_authority" type="external">ambient authority</fr:link> sense, i.e., any process can attempt to perform an action without needing access to any particular capability to do so.</html:p>
                    <html:p>Ambient systems are often contrasted with capability-based ones. In terms of operating systems, this is most commonly illustrated with <fr:link href="/miller2003capability/" title="Capability myths demolished" uri="https://patrick.sirref.org/miller2003capability/" display-uri="miller2003capability" type="local">access-control-lists-as-columns and capabilities-as-rows in an access matrix</fr:link>.</html:p>
                    <html:p><fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> is a capability-based library in OCaml -- resources are provided at the start of the application and can be passed around wherever they are needed. The design is covered in <fr:link href="/talex5/" title="Thomas Leonard" uri="https://patrick.sirref.org/talex5/" display-uri="talex5" type="local">Thomas Leonard</fr:link>'s <fr:link href="https://roscidus.com/blog/blog/2023/04/26/lambda-capabilities/" type="external">lambda capabilities blog post</fr:link>. Unfortunately, any application (willingly or via a third-party library) may ambiently access OS resources. In OCaml, this may be by using the standard library functions (e.g. <html:code>Sys.readdir</html:code>), things from the <html:code>Unix</html:code> module, a C FFI function or by spawning a subprocess.</html:p>
                    <html:p>I had a go at "landlocking" Eio applications last week after stumbling across <fr:link href="https://minimal.dev/" type="external">minimal's</fr:link> <fr:link href="github.com/gominimal/hakoniwa" type="external">hakoniwa</fr:link> library for process isolation (which is very reminiscent of <fr:link href="/void-processes/" title="Vpnkit, Void Processes, LSP Servers › Void Processes " uri="https://patrick.sirref.org/void-processes/" display-uri="void-processes" type="local">void processes</fr:link>).</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="ocaml-keyword-other">module</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Landlock</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio_linux</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Landlock</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-keyword-other">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">/</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Path</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">/</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-keyword">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">run_eio</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">file</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">traceln</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-string-quoted-double">Writing to file</html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Path</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">save</html:span>
                        <html:span class="ocaml-source"> ~</html:span>
                        <html:span class="ocaml-source">create</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-constant-language-polymorphic-variant">`Exclusive</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-numeric-octal-integer">0o644</html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">file</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-string-quoted-double">hello, world!</html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">traceln</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-string-quoted-double">Reading file: </html:span>
                        <html:span class="ocaml-constant-character-printf"><![CDATA[%s]]></html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Path</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">load</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">file</html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Path</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">unlink</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">file</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-keyword">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">bad_program</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-unit"><![CDATA[()]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">In_channel</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">with_open_bin</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-string-quoted-double">/etc/passwd</html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">In_channel</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">input_all</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword-operator">|&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">traceln</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-string-quoted-double">/etc/passwd: </html:span>
                        <html:span class="ocaml-constant-character-printf"><![CDATA[%s]]></html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-keyword-other">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-unit"><![CDATA[()]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio_linux</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">run</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">@@</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">fun</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">env</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Path</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">with_open_dir</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Stdenv</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">cwd</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">env</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">/</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-string-quoted-double">.</html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">@@</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">fun</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">dir</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">parent_fd</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source"> 
</html:span>
                        <html:span class="ocaml-source">    </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio_unix</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Resource</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">fd_opt</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-source">fst</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">dir</html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">|&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Option</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">get</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword-other">in</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">file</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">dir</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">/</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-string-quoted-double">hello.txt</html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">in</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">rules</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">    </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Landlock</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Rule</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">      </html:span>
                        <html:span class="ocaml-source"><![CDATA[[]]></html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">        </html:span>
                        <html:span class="ocaml-source">path_beneath</html:span>
                        <html:span class="ocaml-source"> ~</html:span>
                        <html:span class="ocaml-source">parent_fd</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">          ~</html:span>
                        <html:span class="ocaml-source">flags</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Flags</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Fs</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">            </html:span>
                        <html:span class="ocaml-source">write_file</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">+</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">read_file</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">+</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">make_reg</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">+</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">remove_file</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">           </html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">      </html:span>
                        <html:span class="ocaml-source"><![CDATA[]]]></html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword-other">in</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword-other">match</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Landlock</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">enter</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">rules</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">with</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword-other">|</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Ok</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-unit"><![CDATA[()]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">      </html:span>
                        <html:span class="ocaml-source">run_eio</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">file</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">      </html:span>
                        <html:span class="ocaml-source">bad_program</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-unit"><![CDATA[()]]></html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword-other">|</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Error</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-polymorphic-variant">`Not_supported</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">traceln</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-string-quoted-double">Landlock not supported!</html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-source">
</html:span>
                      </html:code>
                    </html:pre>
                    <html:p>Running this program (with the necessary privileges) prints the following.</html:p>
                    <html:pre><![CDATA[+Writing to file
+Reading file: hello, world!
Fatal error: exception Sys_error("/etc/passwd: Permission denied")]]></html:pre>
                    <html:p>Eio already has a similar function for the BSDs using <fr:link href="https://github.com/ocaml-multicore/eio#filesystem-access" type="external">capsicum</fr:link>. In capsicum, <fr:link href="https://man.freebsd.org/cgi/man.cgi?query=cap_enter" type="external">processes enter a capability mode</fr:link>:</html:p>
                    <html:blockquote>
                      <html:p><html:code>cap_enter()</html:code> places the current process into capability mode, a mode of execution in which processes may	only issue system calls	operating on file descriptors or reading limited global system state.</html:p>
                    </html:blockquote>
                    <html:p>This plays nicely with <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link>'s capabilities. Within the scope of <html:code>Eio.Path.with_open_dir</html:code>, the directory will remain usable after entering "cap" mode. This is not the case with landlock unless you add a specific <html:code>path_beneath</html:code> rule to the ruleset (like in the example above). However, you could imagine plumbing the necessary information throughout the <html:code>Eio_linux</html:code> backend for it to build a set of rules that can be applied at any given moment in order to mimic the capsicum semantics.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>4</fr:month>
                      <fr:day>18</fr:day>
                    </fr:date>
                    <fr:uri>https://patrick.sirref.org/self-host-music/</fr:uri>
                    <fr:display-uri>self-host-music</fr:display-uri>
                    <fr:route>/self-host-music/</fr:route>
                    <fr:title text="Self-hosting Music ">Self-hosting Music </fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>After my friend, <fr:link href="https://gretathompson.com/" type="external">Greta</fr:link>, sent me some articles about music streaming services and their exploitation of artists, I decided enough was enough and setup <fr:link href="https://www.navidrome.org/" type="external">navidrome</fr:link>. It is compatible with <fr:link href="https://www.subsonic.org/pages/api.jsp" type="external">Subsonic</fr:link> and so far I'm very pleased to be streaming my own music to my devices. I am using <fr:link href="https://substreamer.org/" type="external">substreamer</fr:link> on iOS. On my laptop, I tried out <fr:link href="https://github.com/rossberg/camp" type="external">Andreas Rossberg's Camp</fr:link></html:p>
                    <html:blockquote>
                      <html:p>What you got here is an old-school music player heavily inspired by good old <![CDATA[Winamp [1], with a focus on decent music library and playlist handling.]]></html:p>
                    </html:blockquote>
                    <html:p>I took <fr:link href="/anilmadhavapeddy/" title="Anil Madhavapeddy" uri="https://patrick.sirref.org/anilmadhavapeddy/" display-uri="anilmadhavapeddy" type="local">Anil's</fr:link> <fr:link href="https://github.com/avsm/oi" type="external">OCaml Installer</fr:link> tool for a spin with great success... after a few mishaps mostly related to system dependencies.</html:p>
                    <html:pre><![CDATA[$ nix-shell -p pkg-config gcc pulseaudio miniaudio libxi libxrandr libxinerama libffi mesa libxcursor libGL 
$ oi-linux-x86_64 run --with=https://github.com/patricoferris/camp#nix -- camp]]></html:pre>
                    <html:p>The fixes in my fork of Camp are just to install the assets using <html:code>dune</html:code> and also statically provide the <html:code>-lpulse</html:code> flag (which should probably be dynamically picked up). Otherwise, raylib or miniaudio selects the <html:code>Null</html:code> playback device. Roll-on <fr:link href="https://ryan.freumh.org/papers/2026-package-calculus.html" type="external">cross-ecosystem package management</fr:link>.</html:p>
                    <html:img src="/bafkrmifuphhk6ys7hpaqm2jaad6qr7253nevf6ddu4bh7d5tjyasp35kum.png" />
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>4</fr:month>
                  <fr:day>13</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w15/</fr:uri>
                <fr:display-uri>weekly-2026-w15</fr:display-uri>
                <fr:route>/weekly-2026-w15/</fr:route>
                <fr:title text="Moving away from Github">Moving away from Github</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>A short week for me as I was hiking with family in and around <fr:link href="https://en.wikipedia.org/wiki/Porthmadog" type="external">Porthmadog, Wales</fr:link>.</html:p>
                <html:img width="600" src="/bafkrmig6yukypbk4lk2xxq56dm3yfc7i7wm4vrwihpxs37ywu6s63wq3ri.jpg" />
                <fr:tree show-metadata="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>4</fr:month>
                      <fr:day>13</fr:day>
                    </fr:date>
                    <fr:title text="Reliance on Github">Reliance on Github</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>I joined Github in 2016, but only began using it in earnest (according to my Github activity) in July 2018: right after Microsoft announced its intent to acquire Github; and just before <fr:link href="https://www.computerworld.com/article/3535690/microsoft-acquisitions-a-timeline-of-growth-and-a-few-missteps.html" type="external">they closed the $7.5bn deal</fr:link>. I originally joined to boost my résumé and I hoped to receive the odd open-source contribution to projects I was working on (like <fr:link href="https://github.com/patricoferris/ppx_deriving_yaml" type="external">ppx_deriving_yaml</fr:link>). Since then, I have used Github extensively both at work (<fr:link href="https://tarides.com/" type="external">Tarides</fr:link>) and for more personal projects.</html:p>
                    <html:p>There have been many reasons to jump ship from Github ever since the Microsoft acquisition (e.g. <fr:link href="https://github.com/resources/insights/2026-pricing-changes-for-github-actions" type="external">their planned (and quickly unplanned) announcement to charge self-hosted Github runners</fr:link>). Ultimately, I was convinced when I noticed the new AI tool on the homepage with options to select your "versatile and highly intelligent" model. There is no need for me to stay on the platform for my personal projects. AI integration into Github has nullified those earlier motivations for hosting my projects on the site. The number of contributions has increased, but many of them fix superficial elements of an underlying bug. In my (maybe too uncharitable) imagination, it is all too easy for contributors to prompt their <html:em>built-in</html:em> LLM to "fix this broken unit test" and for the fix to layer a single-use workaround on top, instead of identifying and correcting the relevant code. This is, at least, what it looks like to me when I receive a new review request on my open-source projects.</html:p>
                    <html:p>At the end of last week I set up my own Git infrastructure following <fr:link href="/ryangibb/" title="Ryan Gibb" uri="https://patrick.sirref.org/ryangibb/" display-uri="ryangibb" type="local">Ryan's</fr:link> <fr:link href="https://github.com/ryangibb/nixos" type="external">nixos</fr:link> setup. In the future, the source of truth for my projects will be <fr:link href="https://git.sirref.org/" type="external">git.sirref.org</fr:link> (I will likely continue to mirror to <fr:link href="https://tangled.org/patrick.sirref.org/" type="external">tangled.org</fr:link> or Github).</html:p>
                    <fr:tree show-metadata="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>4</fr:month>
                          <fr:day>13</fr:day>
                        </fr:date>
                        <fr:title text="Reduce, Reuse, Recycle">Reduce, Reuse, Recycle</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>Part of self-hosting your own source code is managing some form of automatic testing. I hesitate to describe it as "continuous integration" as the term is confusing and overloaded. Luckily, parts of the OCaml community have been <fr:link href="https://github.com/ocurrent" type="external">building test infrastructure for a while</fr:link>.</html:p>
                        <html:p>I have a proclivity for rebuilding things from scratch be it to learn or to build something the way I want. It is a proclivity I am trying to reign in as I find less and less time to hack on projects. I think this is a good thing. Reusing and recycling existing technologies, in earnest, feels like a pushback on the <fr:link href="https://en.wikipedia.org/wiki/Not_invented_here" type="external">not-invented-here syndrome</fr:link> that seems to be snowballing thanks to LLMs and code generation tools.</html:p>
                        <html:p>To that end, I dusted off my <fr:link href="https://www.ocurrent.org/" type="external">OCurrent</fr:link> knowledge, and repurposed the "local" mode of <fr:link href="https://ocaml.ci.dev/" type="external">OCaml-CI</fr:link> to work with my <fr:link href="https://git.sirref.org/" type="external">git repositories</fr:link>. After battling through nested layers of git submodules, I managed to <html:code>Nix</html:code>-ify this and get it running at <fr:link href="https://tests.sirref.org/" type="external">tests.sirref.org</fr:link>. I needed to add multi-repository support as well as <fr:link href="https://ocaml.org/p/current/0.7.4/doc/current/Current/Monitor/index.html" type="external">Current.Monitor</fr:link>s for the local git refs. By no means perfect, but it is nice to own the testing infrastructure (and the cost of downloading OCaml images and the compute to run the tests).</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>4</fr:month>
                  <fr:day>3</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w14/</fr:uri>
                <fr:display-uri>weekly-2026-w14</fr:display-uri>
                <fr:route>/weekly-2026-w14/</fr:route>
                <fr:title text="Forking in Shells &amp; Library Maintenance">Forking in Shells &amp; Library Maintenance</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>This week, <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> got close to executing <html:code>apt-get install ca-certificates</html:code> and running Debian's <html:code>debootstrap</html:code> scripts correctly. <fr:link href="https://patrick.sirref.org/nathan/" type="external">Nathan</fr:link> and I released <fr:link href="/ppxlib/" title="Ppxlib" uri="https://patrick.sirref.org/ppxlib/" display-uri="ppxlib" type="local">Ppxlib</fr:link> <html:code>0.38.0</html:code>, I added support to <fr:link href="https://github.com/geocaml/ocaml-proj" type="external">ocaml-proj</fr:link> for compiling to the browser and I fixed a long-standing bug in <fr:link href="https://github.com/patricoferris/hilite" type="external">hilite</fr:link>.</html:p>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>4</fr:month>
                      <fr:day>3</fr:day>
                    </fr:date>
                    <fr:title text="Forking in Merry">Forking in Merry</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>So far, <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> has managed to support many features of the POSIX shell specification without needing to do a <html:code>fork(2)</html:code> without an <html:code>exec</html:code>. Or, to put it another way, all the forking that <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> does, will only be followed by running C code. This is actually a feature of <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link>'s <html:code>Process</html:code> API. It is required to make process execution safe in the context of multiple domains in OCaml (mostly due to ensuring consistency within the garbage collector). However, it is perfectly okay to <html:code>fork</html:code> and run OCaml code provided there is only a single domain.</html:p>
                    <html:p>In a shell, many features of the shell language require that commands (or built-ins, function applications etc.) execute within a <html:code>subshell</html:code>. The specification does not go into detail about how shells should implement this, but many choose to <html:code>fork</html:code> in order to preserve some state in the parent (for example, the file descriptor table). Some shells try to minimise the number of forks as an optimisation. The <fr:link href="/korn1996korn/" title="The New Korn Shell" uri="https://patrick.sirref.org/korn1996korn/" display-uri="korn1996korn" type="local">korn shell (ksh)</fr:link> is one such shell.</html:p>
                    <html:blockquote>
                      <html:p>Using the notation <html:code>$(command)</html:code> will cause <html:code>command</html:code> to execute in a subshell of the current ksh. In many instances, ksh will not actually fork/exec a subshell when command is a built-in or a shell function.</html:p>
                    </html:blockquote>
                    <html:p>By trying to adhere to Multicore OCaml's "<html:em>thou shalt not fork</html:em>" commandment, <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> is much more similar to <html:code>ksh</html:code> in this regard. However, whenever there is an interaction with a shell built-in and semantics that need a child process, things get tricky very quickly. Consider the following:</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="sh-keyword-control">while</html:span>
                        <html:span class="sh-meta-scope-while-loop"> </html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-meta-scope-while-loop"> </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">hello</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-while-loop"> </html:span>
                        <html:span class="sh-keyword-control">do</html:span>
                        <html:span class="sh-meta-scope-while-loop">
</html:span>
                        <html:span class="sh-meta-scope-while-loop">  </html:span>
                        <html:span class="sh-support-function-builtin">:</html:span>
                        <html:span class="sh-meta-scope-while-loop">
</html:span>
                        <html:span class="sh-keyword-control">done</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-keyword-operator-pipe">|</html:span>
                        <html:span class="sh-source"> head -n 3
</html:span>
                      </html:code>
                    </html:pre>
                    <html:p>We have a mix of shell built-ins, normal commands, a compound command (the <html:code>while</html:code> loop) and a pipeline. Each individual command in the pipeline requires you to run them in a subshell and set them up <html:em>before</html:em> executing them. Without a <html:code>fork</html:code> for that first command, you may end up looping forever which is not the intended behaviour here. I had a good conversation with <fr:link href="/mdales/" title="Michael W. Dales" uri="https://patrick.sirref.org/mdales/" display-uri="mdales" type="local">Michael</fr:link> about the implications of this for reproducibility.</html:p>
                    <html:p>For now, I have started implementing a <html:code>fork</html:code> for some of the shell features whilst still trying to maintain the functional core of the implementation.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>4</fr:month>
                      <fr:day>3</fr:day>
                    </fr:date>
                    <fr:title text="An OCaml 5.5 compatible Ppxlib">An OCaml 5.5 compatible Ppxlib</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p><fr:link href="/nathanreb/" title="Nathan Rebours" uri="https://patrick.sirref.org/nathanreb/" display-uri="nathanreb" type="local">Nathan</fr:link> and I released an OCaml 5.5 compatible version of <fr:link href="/ppxlib/" title="Ppxlib" uri="https://patrick.sirref.org/ppxlib/" display-uri="ppxlib" type="local">ppxlib</fr:link>.</html:p>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>3</fr:month>
                          <fr:day>30</fr:day>
                        </fr:date>
                        <fr:uri>https://patrick.sirref.org/ppxlib-release-0-38-0/</fr:uri>
                        <fr:display-uri>ppxlib-release-0-38-0</fr:display-uri>
                        <fr:route>/ppxlib-release-0-38-0/</fr:route>
                        <fr:title text="Ppxlib Releases › 0.38.0 "><fr:link href="/ppxlib-releases/" title="Ppxlib Releases" uri="https://patrick.sirref.org/ppxlib-releases/" display-uri="ppxlib-releases" type="local">Ppxlib Releases</fr:link> › 0.38.0 </fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p><fr:link href="/nathanreb/" title="Nathan Rebours" uri="https://patrick.sirref.org/nathanreb/" display-uri="nathanreb" type="local">Nathan</fr:link> and I <fr:link href="https://github.com/ocaml/opam-repository/pull/29563" type="external">released ppxlib.0.38.0</fr:link> last week. Its main feature is full <html:em>migration</html:em> support for the upcoming OCaml 5.5 compiler (currently in its <html:code>alpha3</html:code> release). This means supporting the handful of new features landing in OCaml 5.5: <fr:link href="/modular-explicits/" title="Modular Explicits in OCaml" uri="https://patrick.sirref.org/modular-explicits/" display-uri="modular-explicits" type="local">modular explicits</fr:link>, external type declarations and arbitrary "local" structure items.</html:p>
                        <html:p>As <fr:link href="https://patrick.sirref.org/nathenreb/" type="external">Nathan</fr:link> and I continue to find a plausible maintenance story for <fr:link href="/ppxlib/" title="Ppxlib" uri="https://patrick.sirref.org/ppxlib/" display-uri="ppxlib" type="local">ppxlib</fr:link>, <fr:link href="https://patrick.sirref.org/nathenreb/" type="external">Nathan</fr:link> has opened <fr:link href="https://github.com/ocaml/ocaml/issues/14668" type="external">an issue on the OCaml compiler to discuss the idea of adding additional extension points to the language</fr:link>.</html:p>
                        <html:p>This comes from the desire to be able to store encoded versions of new OCaml features inside older abstract syntax trees.</html:p>
                        <html:p>There are also some nice bug fixes in there too:</html:p>
                        <html:ul>
                          <html:li>
                            <html:p><fr:link href="https://github.com/ocaml-ppx/ppxlib/pull/613" type="external">A potential OOM</fr:link> loop has now been removed.</html:p>
                          </html:li>
                          <html:li>
                            <html:p>
                              <fr:link href="https://github.com/ocaml-ppx/ppxlib/pull/619" type="external">Locations have been restored to long identifiers!</fr:link>
                            </html:p>
                          </html:li>
                        </html:ul>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>4</fr:month>
                      <fr:day>3</fr:day>
                    </fr:date>
                    <fr:uri>https://patrick.sirref.org/ocaml-proj-virt/</fr:uri>
                    <fr:display-uri>ocaml-proj-virt</fr:display-uri>
                    <fr:route>/ocaml-proj-virt/</fr:route>
                    <fr:title text="Browser support for ocaml-proj ">Browser support for ocaml-proj </fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>A while ago <fr:link href="https://github.com/geocaml/ocaml-proj" type="external">I built some "modern" bindings to PROJ4 in OCaml</fr:link>. After reading <fr:link href="https://patrick.sirref.org/jonludlam/" type="external">Jon Ludlam</fr:link>'s <fr:link href="https://jon.recoil.org/blog/2026/03/weeknotes-2026-12.html" type="external">weeknotes</fr:link> (and speaking with him and <fr:link href="/anilmadhavapeddy/" title="Anil Madhavapeddy" uri="https://patrick.sirref.org/anilmadhavapeddy/" display-uri="anilmadhavapeddy" type="local">Anil</fr:link>, I thought it might be nice to add a Javascript backend to those bindings). This was relatively straight-forward using <fr:link href="https://dune.readthedocs.io/en/stable/virtual-libraries.html" type="external">Dune's virtual libraries</fr:link> and is <fr:link href="https://github.com/geocaml/ocaml-proj/blob/main/src/js/proj.ml" type="external">available on Github</fr:link>.</html:p>
                    <html:p>With virtual libraries, your own data analysis could (if you wished) depend solely on the <html:code>proj</html:code> library and later choose to either link it with <html:code>proj.c</html:code> or <html:code>proj.js</html:code> depending on where the analysis is being deployed.</html:p>
                    <html:p>I briefly looked at WASM support, but quickly realised there was not much appetite for it and trying to compile around the C FFI was going to be hard.</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>4</fr:month>
                      <fr:day>3</fr:day>
                    </fr:date>
                    <fr:title text="Hilite updates">Hilite updates</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>Since I helped <fr:link href="https://ocaml.org/" type="external">relaunch the ocaml.org</fr:link> website a few years ago, I have maintained <fr:link href="https://github.com/patricoferris/hilite" type="external">hilite</fr:link>, a tool for doing build-time syntax highligting.</html:p>
                    <html:p>It is being used on the <fr:link href="https://ocaml.org/" type="external">ocaml.org</fr:link> website, <fr:link href="https://github.com/xhtmlboi/yocaml/blob/45858f4b25730149dae3735e7fcdb9111ac1f9eb/yocaml_markdown.opam#L18" type="external">in yocaml_markdown</fr:link> and also in <fr:link href="/mdales/" title="Michael W. Dales" uri="https://patrick.sirref.org/mdales/" display-uri="mdales" type="local">Michael's</fr:link> <fr:link href="https://github.com/mdales/webplats/blob/cf0ea95a66bae52f02a00739eb02564fc94183ee/webplats.opam#L19" type="external">webplats</fr:link> (when it works...).</html:p>
                    <html:p>And it was <fr:link href="/mdales/" title="Michael W. Dales" uri="https://patrick.sirref.org/mdales/" display-uri="mdales" type="local">Michael</fr:link> who led me to finally fixing and adding syntax highlighting support for Python and Go. Once I was on this roll, I finally took a look at fixing the long-standing bug of trying to highlight ocaml-mdx code, like the following:</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="ocaml-mdx-hash">#</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-numeric-decimal-float">50.123</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-mdx-hash">#</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-numeric-decimal-integer">1</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-string-quoted-double">file.ml</html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                      </html:code>
                    </html:pre>
                    <html:p>In fact, this code now highlights fine, but was the source of the bug. The syntax highlighting grammer confuses the ocaml-mdx <html:code>#</html:code> as a toplevel directive and trouble ensues.</html:p>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>3</fr:month>
                  <fr:day>30</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w13/</fr:uri>
                <fr:display-uri>weekly-2026-w13</fr:display-uri>
                <fr:route>/weekly-2026-w13/</fr:route>
                <fr:title text="Ppxlib release and Merry updates">Ppxlib release and Merry updates</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>3</fr:month>
                      <fr:day>30</fr:day>
                    </fr:date>
                    <fr:uri>https://patrick.sirref.org/ppxlib-release-0-38-0/</fr:uri>
                    <fr:display-uri>ppxlib-release-0-38-0</fr:display-uri>
                    <fr:route>/ppxlib-release-0-38-0/</fr:route>
                    <fr:title text="Ppxlib Releases › 0.38.0 "><fr:link href="/ppxlib-releases/" title="Ppxlib Releases" uri="https://patrick.sirref.org/ppxlib-releases/" display-uri="ppxlib-releases" type="local">Ppxlib Releases</fr:link> › 0.38.0 </fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p><fr:link href="/nathanreb/" title="Nathan Rebours" uri="https://patrick.sirref.org/nathanreb/" display-uri="nathanreb" type="local">Nathan</fr:link> and I <fr:link href="https://github.com/ocaml/opam-repository/pull/29563" type="external">released ppxlib.0.38.0</fr:link> last week. Its main feature is full <html:em>migration</html:em> support for the upcoming OCaml 5.5 compiler (currently in its <html:code>alpha3</html:code> release). This means supporting the handful of new features landing in OCaml 5.5: <fr:link href="/modular-explicits/" title="Modular Explicits in OCaml" uri="https://patrick.sirref.org/modular-explicits/" display-uri="modular-explicits" type="local">modular explicits</fr:link>, external type declarations and arbitrary "local" structure items.</html:p>
                    <html:p>As <fr:link href="https://patrick.sirref.org/nathenreb/" type="external">Nathan</fr:link> and I continue to find a plausible maintenance story for <fr:link href="/ppxlib/" title="Ppxlib" uri="https://patrick.sirref.org/ppxlib/" display-uri="ppxlib" type="local">ppxlib</fr:link>, <fr:link href="https://patrick.sirref.org/nathenreb/" type="external">Nathan</fr:link> has opened <fr:link href="https://github.com/ocaml/ocaml/issues/14668" type="external">an issue on the OCaml compiler to discuss the idea of adding additional extension points to the language</fr:link>.</html:p>
                    <html:p>This comes from the desire to be able to store encoded versions of new OCaml features inside older abstract syntax trees.</html:p>
                    <html:p>There are also some nice bug fixes in there too:</html:p>
                    <html:ul>
                      <html:li>
                        <html:p><fr:link href="https://github.com/ocaml-ppx/ppxlib/pull/613" type="external">A potential OOM</fr:link> loop has now been removed.</html:p>
                      </html:li>
                      <html:li>
                        <html:p>
                          <fr:link href="https://github.com/ocaml-ppx/ppxlib/pull/619" type="external">Locations have been restored to long identifiers!</fr:link>
                        </html:p>
                      </html:li>
                    </html:ul>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>3</fr:month>
                      <fr:day>30</fr:day>
                    </fr:date>
                    <fr:title text="Merry updates">Merry updates</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>It turns out writing a POSIX(ish) shell is hard; at least, there is a vast number of slightly obscure features that one needs to support. I think this is surprising because most people writing shell scripts write simple shell scripts; scripts that use a much smaller subset of features. In the same breath, those same developers are likely to use something like <html:code>apt-get install bash</html:code> which runs a plethora of more advanced (and non-POSIX) shell scripts!</html:p>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>3</fr:month>
                          <fr:day>30</fr:day>
                        </fr:date>
                        <fr:title text="Exec redirects">Exec redirects</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>In a shell script, you can use the built-in command <html:code>exec</html:code> to replace the current process with a new one (e.g. <html:code>exec vim</html:code>). However, there is a <fr:link href="https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_21" type="external"><html:em>different</html:em> mode of operation</fr:link> for <html:code>exec</html:code>:</html:p>
                        <html:blockquote>
                          <html:p>If exec is specified with no operands, any redirections associated with the exec command shall be made in the current shell execution environment.</html:p>
                        </html:blockquote>
                        <html:p>One of my litmus tests for <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> is the <fr:link href="https://wiki.debian.org/Debootstrap" type="external">Debian debootstrap scripts</fr:link> (h/t <fr:link href="/anilmadhavapeddy/" title="Anil Madhavapeddy" uri="https://patrick.sirref.org/anilmadhavapeddy/" display-uri="anilmadhavapeddy" type="local">Anil</fr:link>). One thing that it does is the following:</html:p>
                        <html:pre class="hilite">
                          <html:code>
                            <html:span class="sh-entity-name-function">err</html:span>
                            <html:span class="sh-meta-function"> </html:span>
                            <html:span class="sh-punctuation-definition-arguments"><![CDATA[()]]></html:span>
                            <html:span class="sh-meta-function"> </html:span>
                            <html:span class="sh-punctuation-definition-group"><![CDATA[{]]></html:span>
                            <html:span class="sh-meta-scope-group">
</html:span>
                            <html:span class="sh-meta-scope-group">  </html:span>
                            <html:span class="sh-support-function-builtin">printf</html:span>
                            <html:span class="sh-meta-scope-group"> </html:span>
                            <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                            <html:span class="sh-string-quoted-double">err </html:span>
                            <html:span class="sh-punctuation-definition-variable">$</html:span>
                            <html:span class="sh-variable-other-positional">1</html:span>
                            <html:span class="sh-punctuation-definition-string-end">"</html:span>
                            <html:span class="sh-meta-scope-group"> </html:span>
                            <html:span class="sh-keyword-operator-redirect">&gt;&amp;4</html:span>
                            <html:span class="sh-meta-scope-group">
</html:span>
                            <html:span class="sh-punctuation-definition-group"><![CDATA[}]]></html:span>
                            <html:span class="sh-punctuation-definition-function">
</html:span>
                            <html:span class="sh-source">
</html:span>
                            <html:span class="sh-support-function-builtin">exec</html:span>
                            <html:span class="sh-source"> </html:span>
                            <html:span class="sh-keyword-operator-redirect">4&gt;&amp;1</html:span>
                            <html:span class="sh-source"> 
</html:span>
                          </html:code>
                        </html:pre>
                        <html:p>A lot of detail has been elided for clarity. <html:code>exec 4&gt;&amp;1</html:code> sets up a redirection for the shell's execution environment in which file descriptor <html:code>4</html:code> is now an alias for standard output. So, writing to <html:code>4</html:code> (by redirecting a command's standard output to <html:code>4</html:code> i.e. <html:code>&gt;&amp;4</html:code>) will output to where standard output is going (most likely the terminal).</html:p>
                        <html:p>For this to work, shell's must <fr:link href="https://www.man7.org/linux/man-pages/man2/dup.2.html" type="external"><html:code>dup2</html:code></fr:link> the relevant file descriptors which have the following condition:</html:p>
                        <html:blockquote>
                          <html:p>If the file descriptor newfd was previously open, it is closed before being reused; the close is performed silently (i.e., any errors during the close are not reported by dup2()).</html:p>
                        </html:blockquote>
                        <html:p>This is all very well, unless your program has an important file already open that happens to have file descriptor <html:code>4</html:code>. The <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> Linux and POSIX backends suffer from this problem. Both make use of a file-based synchronisation mechanism for waking up the eventloop should another domain push a completion to the scheduler's run queue. On Linux this is <fr:link href="https://github.com/ocaml-multicore/eio/blob/c44ee5ce96c120b7ccc23a12d241dc8672e2888f/lib_eio_linux/sched.ml#L501" type="external">via an eventfd</fr:link> and in <fr:link href="https://github.com/ocaml-multicore/eio/blob/c44ee5ce96c120b7ccc23a12d241dc8672e2888f/lib_eio_posix/sched.ml#L20" type="external">POSIX, a pipe</fr:link>.</html:p>
                        <html:p>This <html:code>dup2</html:code> will close the <html:code>eventfd</html:code> and will likely grind Eio to a halt (or an <html:code>assert false</html:code>). For now, I have resorted to vendoring <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> and moving the <html:code>eventfd</html:code> file descriptors to higher values, though I <fr:link href="https://github.com/ocaml-multicore/eio/pull/836" type="external">have opened a PR to make this more configurable</fr:link>. This bug is quite easy to write up... it was not so easy to find!</html:p>
                        <html:p>As a random example, consider the <html:code>debconf/confmodule</html:code> <fr:link href="https://sources.debian.org/src/debconf/1.5.77/confmodule/" type="external">script</fr:link> which offers very little room for a buggy implementation!</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>3</fr:month>
                      <fr:day>30</fr:day>
                    </fr:date>
                    <fr:title text="Outreachy">Outreachy</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>The presentations from the demonstrations for this round of Outreachy are now online!</html:p>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>2</fr:month>
                          <fr:day>9</fr:day>
                        </fr:date>
                        <fr:uri>https://patrick.sirref.org/outreachy-ocaml-tiff/</fr:uri>
                        <fr:display-uri>outreachy-ocaml-tiff</fr:display-uri>
                        <fr:route>/outreachy-ocaml-tiff/</fr:route>
                        <fr:title text="Write support in OCaml TIFF library">Write support in OCaml TIFF library</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>I am mentoring <fr:link href="/tambe-salome/" title="Tambe Salome" uri="https://patrick.sirref.org/tambe-salome/" display-uri="tambe-salome" type="local">Tambe Salome</fr:link> during the December 2025 Outreachy round to add support for writing TIFF files in the <fr:link href="/ocaml-tiff/" title="ocaml-tiff" uri="https://patrick.sirref.org/ocaml-tiff/" display-uri="ocaml-tiff" type="local">ocaml-tiff</fr:link> library.</html:p>
                        <html:p>You can now see the video of the demonstration day presentation:</html:p>
                        <html:div style="text-align: center">
<html:iframe title="Outreachy Demo Day December 2025 Round" width="560" height="315" src="https://watch.ocaml.org/videos/embed/8aUqMhFvhQGq4WJLH3ukjA?start=1h18m33s" frameborder="0" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups allow-forms" />
</html:div>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" expanded="false" toc="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>3</fr:month>
                  <fr:day>19</fr:day>
                </fr:date>
                <fr:uri>https://patrick.sirref.org/weekly-2026-w12/</fr:uri>
                <fr:display-uri>weekly-2026-w12</fr:display-uri>
                <fr:route>/weekly-2026-w12/</fr:route>
                <fr:title text="A POSIX Shell in OCaml">A POSIX Shell in OCaml</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>Long time, no weekly. Since the start of this year I have been building a POSIX shell in OCaml called <html:code>msh</html:code> (with the underlying library called <html:code>Merry</html:code>). <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> is available online now.</html:p>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>3</fr:month>
                      <fr:day>19</fr:day>
                    </fr:date>
                    <fr:title text="A POSIX(ish) shell in OCaml">A POSIX(ish) shell in OCaml</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p><fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> is a POSIX(ish) in OCaml. It uses  <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> alongside <fr:link href="github.com/colis-anr/morbig" type="external">Morbig</fr:link> (a static parser for POSIX shell).</html:p>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>3</fr:month>
                          <fr:day>19</fr:day>
                        </fr:date>
                        <fr:title text="Why another (POSIX) shell?">Why another (POSIX) shell?</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>Shells have been around for a long time. In my research on the notion of <html:em>metashell</html:em> I wrote about Louis Pouzin originally coining the term:</html:p>
                        <fr:tree show-metadata="false" numbered="false">
                          <fr:frontmatter>
                            <fr:authors>
                              <fr:author>
                                <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                              </fr:author>
                            </fr:authors>
                            <fr:date>
                              <fr:year>2025</fr:year>
                              <fr:month>5</fr:month>
                              <fr:day>19</fr:day>
                            </fr:date>
                            <fr:uri>https://patrick.sirref.org/pouzin-shell/</fr:uri>
                            <fr:display-uri>pouzin-shell</fr:display-uri>
                            <fr:route>/pouzin-shell/</fr:route>
                            <fr:title text="Metashells › Louis Pouzin's &quot;SHELL&quot; "><fr:link href="/weekly-2025-05-12/" title="Metashells" uri="https://patrick.sirref.org/weekly-2025-05-12/" display-uri="weekly-2025-05-12" type="local">Metashells</fr:link> › Louis Pouzin's "SHELL" </fr:title>
                          </fr:frontmatter>
                          <fr:mainmatter>
                            <html:p>I spent some time reading <fr:link href="/pouzin-shell-2013/" title="The Origin of the Shell" uri="https://patrick.sirref.org/pouzin-shell-2013/" display-uri="pouzin-shell-2013" type="local">part of the multics design documentation</fr:link> this week. Louis Pouzin coined the term "SHELL" in this document, and I was reminded yet again just how important it is to be a good writer even as a "computer science researcher". For example, this excerpt from the requirements section of the document</html:p>
                            <html:blockquote>
                              <html:p>The previous definitions imply that a command MUST be designed while keeping in mind the user, sitting at his console, wondering about what might be going on, mistyping or forgetting arguments, even if fully aware of the conventions, and possibly interfering with the command by hasty quits, carriage returns, and other temperamental reactions.</html:p>
                            </html:blockquote>
                            <html:p>And then later, when defining the "SHELL".</html:p>
                            <html:blockquote>
                              <html:p>We may envision a common procedure called automatically by the supervisor whenever a user types in some message at his console, at a time when he has no other process in active execution under console control (presently called command level). This procedure acts as an interface between console messages and subroutine. The purpose of such a procedure is to create a medium of exchange into which one could activate any procedure, <html:em>inside of another program if it were called</html:em>. Hereafter, for simplification, we shall refer to that procedure as the "SHELL".</html:p>
                            </html:blockquote>
                            <html:p>It still surprises how little the undergraduate degree in computer science at <fr:link href="/ucam/" title="University of Cambridge" uri="https://patrick.sirref.org/ucam/" display-uri="ucam" type="local">Cambridge</fr:link> focuses on writing skills.</html:p>
                          </fr:mainmatter>
                        </fr:tree>
                        <html:p>I built <fr:link href="/shelter/" title="Shelter" uri="https://patrick.sirref.org/shelter/" display-uri="shelter" type="local">Shelter</fr:link> exploring the idea of a shell-like interface that allowed users to <html:em>undo</html:em> their shell actions (amongst other cool tricks). Unfortunately <html:em>shell-like</html:em> is not enough. <fr:link href="/shelter/" title="Shelter" uri="https://patrick.sirref.org/shelter/" display-uri="shelter" type="local">Shelter</fr:link> cut many corners to masquerade as a shell (e.g. appending <html:code>env</html:code> to understand how a command may have altered the execution environment). I felt it was necessary to make <fr:link href="/shelter/" title="Shelter" uri="https://patrick.sirref.org/shelter/" display-uri="shelter" type="local">Shelter</fr:link> a "SHELL"! To do that, I needed a solid foundation to build on.</html:p>
                        <html:p><html:code>msh</html:code>, the POSIX shell that comes with <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>, is by no means POSIX-complete in terms of features. But it is my <fr:link href="https://github.com/patricoferris/nixos/blob/e0faf870f76710d4a75ace775f333c88f1321c5a/modules/default.nix#L59" type="external">daily driver at this point</fr:link>. It includes a pure OCaml rewrite of <fr:link href="github.com/antirez/linenoise" type="external">linenoise</fr:link> (a small, self-contained alternative to the venerable <html:code>readline</html:code>) called <fr:link href="https://tangled.org/patrick.sirref.org/bruit" type="external">bruit</fr:link>.</html:p>
                        <html:p>If you have <html:code>docker</html:code> installed on your machine, you can take it for a spin today:</html:p>
                        <html:pre><![CDATA[docker run -it --rm patrickferris/msh]]></html:pre>
                        <html:p>The <html:code>patrickferris/msh</html:code> docker image is just for trying it out. It is based on the OCaml 5.3 alpine image.</html:p>
                        <html:p>Alternatively, you can build <html:code>msh</html:code> from source and have it available in your opam switch.</html:p>
                        <html:pre><![CDATA[opam pin git+https://tangled.org/patrick.sirref.org/merry]]></html:pre>
                        <html:p>There are many small paper cuts left to patch over, but most of it is porcelain (e.g. <html:code>ctrl+left-arrow</html:code> for moving in <fr:link href="https://tangled.org/patrick.sirref.org/bruit" type="external">bruit</fr:link>). Unfortunately, these are the kinds of things that you will <html:em>immediately</html:em> stumble upon.</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>3</fr:month>
                          <fr:day>19</fr:day>
                        </fr:date>
                        <fr:title text="What makes Merry different?">What makes Merry different?</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>Nothing.</html:p>
                        <html:p><fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> is supposed to be a solid, POSIX-ish base to build on. Unfortunaltely, as it turns out, the subset of features from the POSIX specification that people <html:em>actually use</html:em>... is pretty much all of it. Every possible redirection, variable expansion, shell built-in and compound command make some appearance. Not to mention the non-POSIX bits of shell we all take for granted (e.g. <html:code>&amp;&gt;</html:code>-redirection).</html:p>
                        <html:p><fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> and <html:code>msh</html:code> are useable. You will most likely find bugs if you use them. If, when using <html:code>msh</html:code>, you find something obscure happening you can enable debug mode either my setting the variable <html:code>MSH_DEBUG</html:code> or by invoking <html:code>msh</html:code> with <html:code>-v -v</html:code>.</html:p>
                      </fr:mainmatter>
                    </fr:tree>
                    <fr:tree show-metadata="false" numbered="false">
                      <fr:frontmatter>
                        <fr:authors>
                          <fr:author>
                            <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                          </fr:author>
                        </fr:authors>
                        <fr:date>
                          <fr:year>2026</fr:year>
                          <fr:month>3</fr:month>
                          <fr:day>19</fr:day>
                        </fr:date>
                        <fr:title text="What's next?">What's next?</fr:title>
                      </fr:frontmatter>
                      <fr:mainmatter>
                        <html:p>It is soon time to combine <fr:link href="/shelter/" title="Shelter" uri="https://patrick.sirref.org/shelter/" display-uri="shelter" type="local">Shelter</fr:link> and <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> into the time-travelling, POSIX-ish shell that I have been trying to build since I first started working on <fr:link href="/shelter/" title="Shelter" uri="https://patrick.sirref.org/shelter/" display-uri="shelter" type="local">Shelter</fr:link>.</html:p>
                        <html:p>For this to be successful, I need <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> to be able to pass plenty of tests and right now that involves trying to install plenty of packages using tools like <html:code>apk</html:code> and <html:code>apt</html:code>.</html:p>
                        <fr:tree show-metadata="false" numbered="false">
                          <fr:frontmatter>
                            <fr:authors>
                              <fr:author>
                                <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                              </fr:author>
                            </fr:authors>
                            <fr:date>
                              <fr:year>2026</fr:year>
                              <fr:month>3</fr:month>
                              <fr:day>19</fr:day>
                            </fr:date>
                            <fr:title text="Shell MRDT">Shell MRDT</fr:title>
                          </fr:frontmatter>
                          <fr:mainmatter>
                            <html:p>What I am particularly interested in reasoning about, is the <fr:link href="https://tangled.org/patrick.sirref.org/merry/blob/main/src/lib/eval.ml#L22" type="external">execution context</fr:link> in <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link>. This value, alongside the file-system, constitutes a fairly deep understanding of the state that changes in each step of a shell's evaluation loop.</html:p>
                            <html:p>This was, in terms of <fr:link href="/shelter/" title="Shelter" uri="https://patrick.sirref.org/shelter/" display-uri="shelter" type="local">Shelter</fr:link>, the missing piece for truly building some kind of <fr:link href="/mrdts/" title="Mergeable Replicated Data Type Implementation" uri="https://patrick.sirref.org/mrdts/" display-uri="mrdts" type="local">MRDT</fr:link> across shell sessions.</html:p>
                          </fr:mainmatter>
                        </fr:tree>
                      </fr:mainmatter>
                    </fr:tree>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>3</fr:month>
                      <fr:day>19</fr:day>
                    </fr:date>
                    <fr:title text="TIFF in OCaml">TIFF in OCaml</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>I picked up from the excellent <fr:link href="/outreachy/" title="Outreachy" uri="https://patrick.sirref.org/outreachy/" display-uri="outreachy" type="local">Outreachy</fr:link> work of <fr:link href="https://patrick.sirref.org/tambe salome/" type="external">Tambe Salome</fr:link> in getting write-support in <fr:link href="/ocaml-tiff/" title="ocaml-tiff" uri="https://patrick.sirref.org/ocaml-tiff/" display-uri="ocaml-tiff" type="local">ocaml-tiff</fr:link>. We are getting closer to the kind of API I envisaged in this latest round of refinement and review.</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="ocaml-keyword-other">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">/</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Path</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">/</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-keyword">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">checkerboard</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">~</html:span>
                        <html:span class="ocaml-source">size</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">v</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Nx</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">zeros</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Nx</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">uint8</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[[|]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">size</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">size</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[|]]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">in</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword">for</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">row</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-numeric-decimal-integer">0</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">to</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">size</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-numeric-decimal-integer">1</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">do</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">    </html:span>
                        <html:span class="ocaml-keyword">for</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">col</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-numeric-decimal-integer">0</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">to</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">size</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-numeric-decimal-integer">1</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">do</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">      </html:span>
                        <html:span class="ocaml-keyword-other">if</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-source">row</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">+</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">col</html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">mod</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-numeric-decimal-integer">2</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-numeric-decimal-integer">0</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">then</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Nx</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">set_item</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[[]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">row</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">col</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[]]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-numeric-decimal-integer">254</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">v</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">    </html:span>
                        <html:span class="ocaml-keyword-other">done</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword-other">done</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-separator-terminator punctuation-separator">;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Nx</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">to_bigarray</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">v</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-keyword-other">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-unit"><![CDATA[()]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Eio_posix</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">run</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">@@</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">fun</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">env</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Tiff_eio</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">with_open_out</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-source">env</html:span>
                        <html:span class="ocaml-keyword-other">#</html:span>
                        <html:span class="ocaml-source">cwd</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">/</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-string-quoted-double">example.tiff</html:span>
                        <html:span class="ocaml-string-quoted-double">"</html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">@@</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">fun</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">w</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword">let</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">tif</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">=</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Tiff</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">make</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-source">checkerboard</html:span>
                        <html:span class="ocaml-source"> ~</html:span>
                        <html:span class="ocaml-source">size</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-constant-numeric-decimal-integer">256</html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">in</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Tiff</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">to_file</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">tif</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">w</html:span>
                        <html:span class="ocaml-source">
</html:span>
                      </html:code>
                    </html:pre>
                    <html:img src="/bafkrmiaqqczj5lemda5ijfjtjsyldbhmveu3btfvoa7rpt5k4dvdejjuhu.png" />
                    <html:p>I have also been extremely pleased to see further external collaborators appearing:</html:p>
                    <html:ul>
                      <html:li>
                        <html:p><fr:link href="https://github.com/geocaml/ocaml-tiff/pull/63" type="external">Nicolas</fr:link> helping out with metadata maintainence.</html:p>
                      </html:li>
                      <html:li>
                        <html:p><fr:link href="https://github.com/geocaml/ocaml-tiff/pull/62" type="external">Virgile</fr:link> adding support for reading multi-image TIFF files.</html:p>
                      </html:li>
                    </html:ul>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
          </fr:mainmatter>
        </fr:tree>
      </fr:mainmatter>
    </fr:tree>
    <fr:tree show-metadata="false" hidden-when-empty="true">
      <fr:frontmatter>
        <fr:authors />
        <fr:title text="Backlinks">Backlinks</fr:title>
      </fr:frontmatter>
      <fr:mainmatter>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
              </fr:author>
            </fr:authors>
            <fr:date>
              <fr:year>2026</fr:year>
              <fr:month>7</fr:month>
              <fr:day>12</fr:day>
            </fr:date>
            <fr:uri>https://patrick.sirref.org/ocaml-roundup-august-2026/</fr:uri>
            <fr:display-uri>ocaml-roundup-august-2026</fr:display-uri>
            <fr:route>/ocaml-roundup-august-2026/</fr:route>
            <fr:title text="OCaml Roundup: August 2026">OCaml Roundup: August 2026</fr:title>
          </fr:frontmatter>
          <fr:mainmatter>
            <html:p>Much of August was spent thinking about POSIX shell semantics, and in particular extending <fr:link href="/greenberg2020smoosh/" title="Executable formal semantics for the POSIX shell" uri="https://patrick.sirref.org/greenberg2020smoosh/" display-uri="greenberg2020smoosh" type="local">notions from smoosh</fr:link> to include side effects (and maybe concurrency). This is still nascent work, but some ideas and flavours are described below.</html:p>
            <fr:tree show-metadata="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>7</fr:month>
                  <fr:day>12</fr:day>
                </fr:date>
                <fr:title text="Merging Shells">Merging Shells</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>The work came from wanting to merge two shell sessions into one. Setting aside the practicalities of an implementation, it makes sense to start first theoretically. When can I merge two shell executions, and when can I not?</html:p>
                <html:div style="text-align: center;">
<html:img height="400px" src="/bafkrmibyvel7ut7rkmhcagizkntkh2hz5urf7rzif7py5zh7a4nntrv5u4.png" />
</html:div>
                <html:p>It would be nice to think of <html:code>unset DOWNLOAD_DIR</html:code> as a function. This function takes the shell state and the operating state as arguments and returns a modified shell state and an "unmodified" operating system state (at least for our purposes). Meanwhile, <html:code>branch-b</html:code> may have a lot more side effects, but when focusing on the environment only we can see in a sense the two branches are conflict free. Well, sorta. We also need to ensure we do not also read <html:code>DOWNLOAD_DIR</html:code> in <html:code>branch-b</html:code> if we intend to remove it in their merge.</html:p>
                <html:div style="text-align: center;">
<html:img height="300px" src="/bafkrmibympwhn47ko6ogugzj7kcqo7uqoo2xzjqrfxmcems27umlkfigeq.png" />
</html:div>
                <html:p>This work, in part, is borrowing ideas from <fr:link href="/kcrsk-mrdts-2022/" title="Certified mergeable replicated data types" uri="https://patrick.sirref.org/kcrsk-mrdts-2022/" display-uri="kcrsk-mrdts-2022" type="local">MRDTs</fr:link> and can reuse <fr:link href="/void-process/" title="Void Process" uri="https://patrick.sirref.org/void-process/" display-uri="void-process" type="local">void processes</fr:link> to help reason about when side effects can occur, as well as <fr:link href="/open-trace/" title="Opentrace" uri="https://patrick.sirref.org/open-trace/" display-uri="open-trace" type="local">open-trace</fr:link> (which is now becoming <html:code>os-trace</html:code>).</html:p>
                <html:p>I hope to continue this work, see my <fr:link href="/weekly-2026-w37/" title="Analysing some Dockerfiles" uri="https://patrick.sirref.org/weekly-2026-w37/" display-uri="weekly-2026-w37" type="local">latest weekly</fr:link> for more on this.</html:p>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>7</fr:month>
                  <fr:day>12</fr:day>
                </fr:date>
                <fr:title text="Eio in the browser">Eio in the browser</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>I spent some time carving out <html:code>Eio_browser</html:code> into a standalone library, something I depend on for <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> to work <fr:link href="https://patrick.sirref.org/msh" type="external">in the browser</fr:link>.</html:p>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>9</fr:month>
                      <fr:day>12</fr:day>
                    </fr:date>
                    <fr:uri>https://patrick.sirref.org/eio_browser/</fr:uri>
                    <fr:display-uri>eio_browser</fr:display-uri>
                    <fr:route>/eio_browser/</fr:route>
                    <fr:title text="Eio_browser">Eio_browser</fr:title>
                    <fr:meta name="external">https://git.sirref.org/eio_browser</fr:meta>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p><html:code>Eio_browser</html:code> is a backend for <fr:link href="/eio/" title="Eio" uri="https://patrick.sirref.org/eio/" display-uri="eio" type="local">Eio</fr:link> that works in the browser. It <fr:link href="https://github.com/ocaml-multicore/eio_js" type="external">reuses work I started</fr:link> at <fr:link href="https://patrick.sirref.org/tarides/" type="external">Tarides</fr:link> that picked up some momentum but has since been a little dormant.</html:p>
                    <html:p>Crucially, it provides a functional standard environment like <html:code>Eio_main</html:code> or any of the OS-specific backends.</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="ocaml-keyword-other">module</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Stdenv</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other">sig</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">  </html:span>
                        <html:span class="ocaml-keyword">type</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-entity-name-function-binding">base</html:span>
                        <html:span class="ocaml-source"> =
</html:span>
                        <html:span class="ocaml-source">    &lt; stdout : Eio_unix.sink_ty r
</html:span>
                        <html:span class="ocaml-source">    ; stderr : Eio_unix.sink_ty r
</html:span>
                        <html:span class="ocaml-source">    ; clock : float Eio.Time.clock_ty r
</html:span>
                        <html:span class="ocaml-source">    ; mono_clock : Eio.Time.Mono.ty r
</html:span>
                        <html:span class="ocaml-source">    ; secure_random : Eio.Flow.source_ty r
</html:span>
                        <html:span class="ocaml-source">    ; debug : Eio.Debug.t
</html:span>
                        <html:span class="ocaml-source">    ; backend_id : string &gt;
</html:span>
                        <html:span class="ocaml-keyword-other">end</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-keyword-other">val</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source">run</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-colon punctuation">:</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-source"><![CDATA[(]]></html:span>
                        <html:span class="ocaml-constant-language-capital-identifier">Stdenv</html:span>
                        <html:span class="ocaml-keyword-other-ocaml punctuation-other-period punctuation-separator">.</html:span>
                        <html:span class="ocaml-source">base</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-support-type">unit</html:span>
                        <html:span class="ocaml-source"><![CDATA[)]]></html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-keyword-operator">-&gt;</html:span>
                        <html:span class="ocaml-source"> </html:span>
                        <html:span class="ocaml-support-type">unit</html:span>
                        <html:span class="ocaml-source">
</html:span>
                        <html:span class="ocaml-comment-doc"><![CDATA[(**]]></html:span>
                        <html:span class="ocaml-comment-doc"><![CDATA[ [run fn] runs [fn] passing it the {! Stdenv.base} environment.]]></html:span>
                        <html:span class="ocaml-comment-doc">
</html:span>
                        <html:span class="ocaml-comment-doc"><![CDATA[    See {! Backend.run} for more information about how effects are handled.]]></html:span>
                        <html:span class="ocaml-comment-doc"><![CDATA[*)]]></html:span>
                        <html:span class="ocaml-source">
</html:span>
                      </html:code>
                    </html:pre>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
          </fr:mainmatter>
        </fr:tree>
      </fr:mainmatter>
    </fr:tree>
    <fr:tree show-metadata="false" hidden-when-empty="true">
      <fr:frontmatter>
        <fr:authors />
        <fr:title text="Related">Related</fr:title>
      </fr:frontmatter>
      <fr:mainmatter>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors />
            <fr:uri>https://patrick.sirref.org/anilmadhavapeddy/</fr:uri>
            <fr:display-uri>anilmadhavapeddy</fr:display-uri>
            <fr:route>/anilmadhavapeddy/</fr:route>
            <fr:title text="Anil Madhavapeddy">Anil Madhavapeddy</fr:title>
            <fr:taxon>person</fr:taxon>
            <fr:meta name="external">https://anil.recoil.org</fr:meta>
          </fr:frontmatter>
          <fr:mainmatter>
            <html:p>Professor of Planetary Computing at <fr:link href="/ucam/" title="University of Cambridge" uri="https://patrick.sirref.org/ucam/" display-uri="ucam" type="local">University of Cambridge</fr:link>.</html:p>
          </fr:mainmatter>
        </fr:tree>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors />
            <fr:uri>https://patrick.sirref.org/dra27/</fr:uri>
            <fr:display-uri>dra27</fr:display-uri>
            <fr:route>/dra27/</fr:route>
            <fr:title text="David Allsopp">David Allsopp</fr:title>
            <fr:taxon>person</fr:taxon>
            <fr:meta name="external">https://www.dra27.uk/blog/</fr:meta>
          </fr:frontmatter>
          <fr:mainmatter />
        </fr:tree>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors />
            <fr:uri>https://patrick.sirref.org/eio/</fr:uri>
            <fr:display-uri>eio</fr:display-uri>
            <fr:route>/eio/</fr:route>
            <fr:title text="Eio">Eio</fr:title>
            <fr:taxon>Software</fr:taxon>
            <fr:meta name="external">https://github.com/ocaml-multicore/eio</fr:meta>
          </fr:frontmatter>
          <fr:mainmatter>
            <html:blockquote>
              <html:p>Eio provides an effects-based direct-style IO stack for OCaml 5</html:p>
            </html:blockquote>
          </fr:mainmatter>
        </fr:tree>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors />
            <fr:uri>https://patrick.sirref.org/nathanreb/</fr:uri>
            <fr:display-uri>nathanreb</fr:display-uri>
            <fr:route>/nathanreb/</fr:route>
            <fr:title text="Nathan Rebours">Nathan Rebours</fr:title>
            <fr:taxon>person</fr:taxon>
            <fr:meta name="external">https://github.com/nathanreb</fr:meta>
          </fr:frontmatter>
          <fr:mainmatter>
            <html:p>Maintainer of <fr:link href="/ppxlib/" title="Ppxlib" uri="https://patrick.sirref.org/ppxlib/" display-uri="ppxlib" type="local">Ppxlib</fr:link>.</html:p>
          </fr:mainmatter>
        </fr:tree>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors />
            <fr:uri>https://patrick.sirref.org/ppxlib/</fr:uri>
            <fr:display-uri>ppxlib</fr:display-uri>
            <fr:route>/ppxlib/</fr:route>
            <fr:title text="Ppxlib">Ppxlib</fr:title>
            <fr:meta name="external">https://github.com/ocaml-ppx/ppxlib</fr:meta>
          </fr:frontmatter>
          <fr:mainmatter>
            <html:p>An OCaml library and ecosystem for preprocessing.</html:p>
          </fr:mainmatter>
        </fr:tree>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors />
            <fr:uri>https://patrick.sirref.org/shelter/</fr:uri>
            <fr:display-uri>shelter</fr:display-uri>
            <fr:route>/shelter/</fr:route>
            <fr:title text="Shelter">Shelter</fr:title>
            <fr:meta name="external">https://tangled.sh/@patrick.sirref.org/shelter</fr:meta>
          </fr:frontmatter>
          <fr:mainmatter>
            <html:p>A shim between a user and the shell that provides greater reproducibility and insight into what your code is doing!</html:p>
            <fr:tree show-metadata="false" numbered="false">
              <fr:frontmatter>
                <fr:authors />
                <fr:title text="Give me Shelter!">Give me Shelter!</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>Shelter combines the caching and reproducibility of Dockerfiles with the ease of interaction of the shell. I'd be interested to use <fr:link href="/ryangibb/" title="Ryan Gibb" uri="https://patrick.sirref.org/ryangibb/" display-uri="ryangibb" type="local">Ryan</fr:link>'s <fr:link href="https://ryan.freumh.org/research.html" type="external">package management work</fr:link> as a means to specify the base environment users want.</html:p>
                <html:p>Here is an example of running Shelter and making use of the branchable sessions.</html:p>
                <html:pre class="ansi2html-content"><html:span class="ansi33">shelter&gt; </html:span>echo hello &gt; hello.txt
<html:span class="ansi33">shelter</html:span><![CDATA[[]]><html:span class="ansi32">main</html:span>#<html:span class="ansi35">bbd691a</html:span><![CDATA[] : { mode:]]><html:span class="ansi31">rw</html:span><![CDATA[ }> @ session exp-1]]><html:span class="ansi33">shelter</html:span><![CDATA[[]]><html:span class="ansi32">exp-1</html:span>#<html:span class="ansi35">bbd691a</html:span><![CDATA[] : { mode:]]><html:span class="ansi31">rw</html:span><![CDATA[ }> echo world >> hello.txt]]><html:span class="ansi33">shelter</html:span><![CDATA[[]]><html:span class="ansi32">exp-1</html:span>#<html:span class="ansi35">dd5bab8</html:span><![CDATA[] : { mode:]]><html:span class="ansi31">rw</html:span><![CDATA[ }> @ session main]]><html:span class="ansi33">shelter</html:span><![CDATA[[]]><html:span class="ansi32">main</html:span>#<html:span class="ansi35">bbd691a</html:span><![CDATA[] : { mode:]]><html:span class="ansi31">rw</html:span><![CDATA[ }> echo "to the" >> hello.txt]]><html:span class="ansi33">shelter</html:span><![CDATA[[]]><html:span class="ansi32">main</html:span>#<html:span class="ansi35">b9abef4</html:span><![CDATA[] : { mode:]]><html:span class="ansi31">rw</html:span><![CDATA[ }> cat hello.txt
hello
to the]]><html:span class="ansi33">shelter</html:span><![CDATA[[]]><html:span class="ansi32">main</html:span>#<html:span class="ansi35">d1c1728</html:span><![CDATA[] : { mode:]]><html:span class="ansi31">rw</html:span><![CDATA[ }> @ session exp-1]]><html:span class="ansi33">shelter</html:span><![CDATA[[]]><html:span class="ansi32">exp-1</html:span>#<html:span class="ansi35">dd5bab8</html:span><![CDATA[] : { mode:]]><html:span class="ansi31">rw</html:span><![CDATA[ }> @ replay main]]><html:span class="ansi33">shelter</html:span><![CDATA[[]]><html:span class="ansi32">exp-1</html:span>#<html:span class="ansi35">df1b4e1</html:span><![CDATA[] : { mode:]]><html:span class="ansi31">rw</html:span><![CDATA[ }> cat hello.txt
hello
to the
world]]><html:span class="ansi33">shelter</html:span><![CDATA[[]]><html:span class="ansi32">exp-1</html:span>#<html:span class="ansi35">67162b7</html:span><![CDATA[] : { mode:]]><html:span class="ansi31">rw</html:span><![CDATA[ }>]]></html:pre>
                <html:p>The <html:code>@</html:code> character allows the user to interact directly with Shelter's build-in operators. <html:code>@ session exp-1</html:code> creates a new session as there is no <html:code>exp-1</html:code> session. It points to the head commit of the branch it came from (<html:code>git checkout -b exp-1</html:code>).</html:p>
                <html:p>From there we make changes and switch between branches until we perform a replay. A replay is a rebase without any merge conflict checks, it simply finds the <html:em>least common ancestor</html:em> of the two branches and re-applies the commits from your current branch (here <html:code>exp-1</html:code>) onto the head of the target branch (here <html:code>main</html:code>).</html:p>
              </fr:mainmatter>
            </fr:tree>
            <fr:tree show-metadata="false" numbered="false">
              <fr:frontmatter>
                <fr:authors />
                <fr:title text="Under the hood">Under the hood</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>Shelter is a light-weight, reimplementation of <fr:link href="https://github.com/ocurrent/obuilder" type="external">obuilder</fr:link>. It uses the similar ideas, like snapshotting filesystems (e.g. <fr:link href="https://github.com/patricoferris/ocaml-zfs" type="external">ZFS</fr:link>) and Linux namespaces (e.g. <fr:link href="https://github.com/quantifyearth/void" type="external">Void</fr:link>). Between the low-level store and the interactive shell parts, there is an <fr:link href="https://github.com/mirage/irmin" type="external">Irmin</fr:link> store.</html:p>
              </fr:mainmatter>
            </fr:tree>
          </fr:mainmatter>
        </fr:tree>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors />
            <fr:uri>https://patrick.sirref.org/samoht/</fr:uri>
            <fr:display-uri>samoht</fr:display-uri>
            <fr:route>/samoht/</fr:route>
            <fr:title text="Thomas Gazagnaire">Thomas Gazagnaire</fr:title>
            <fr:taxon>person</fr:taxon>
            <fr:meta name="external">https://gazagnaire.org/blog.html</fr:meta>
          </fr:frontmatter>
          <fr:mainmatter />
        </fr:tree>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors />
            <fr:uri>https://patrick.sirref.org/talex5/</fr:uri>
            <fr:display-uri>talex5</fr:display-uri>
            <fr:route>/talex5/</fr:route>
            <fr:title text="Thomas Leonard">Thomas Leonard</fr:title>
            <fr:taxon>person</fr:taxon>
            <fr:meta name="external">https://roscidus.com/</fr:meta>
          </fr:frontmatter>
          <fr:mainmatter />
        </fr:tree>
        <fr:tree show-metadata="true" expanded="false" toc="false" numbered="false">
          <fr:frontmatter>
            <fr:authors>
              <fr:author>
                <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
              </fr:author>
            </fr:authors>
            <fr:date>
              <fr:year>2026</fr:year>
              <fr:month>7</fr:month>
              <fr:day>4</fr:day>
            </fr:date>
            <fr:uri>https://patrick.sirref.org/merry-benchmarks/</fr:uri>
            <fr:display-uri>merry-benchmarks</fr:display-uri>
            <fr:route>/merry-benchmarks/</fr:route>
            <fr:title text="Shell Benchmarks, Back to Writing › Benchmarking Merry "><fr:link href="/weekly-2026-w26/" title="Shell Benchmarks, Back to Writing" uri="https://patrick.sirref.org/weekly-2026-w26/" display-uri="weekly-2026-w26" type="local">Shell Benchmarks, Back to Writing</fr:link> › Benchmarking Merry </fr:title>
          </fr:frontmatter>
          <fr:mainmatter>
            <fr:tree show-metadata="false" numbered="false">
              <fr:frontmatter>
                <fr:authors>
                  <fr:author>
                    <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                  </fr:author>
                </fr:authors>
                <fr:date>
                  <fr:year>2026</fr:year>
                  <fr:month>7</fr:month>
                  <fr:day>4</fr:day>
                </fr:date>
                <fr:title text="Medium-to-small potatoes">Medium-to-small potatoes</fr:title>
              </fr:frontmatter>
              <fr:mainmatter>
                <html:p>I used <fr:link href="https://github.com/shellspec/shellbench" type="external">shellbench</fr:link> to understand where some of the performance pitfalls were coming from.</html:p>
                <html:pre class="hilite">
                  <html:code>
                    <html:span class="sh-source">$ ./shellbench -s bash,zsh,msh sample/func.sh
</html:span>
                    <html:span class="sh-source">-----------------------------------------------------------------------------------
</html:span>
                    <html:span class="sh-source">name                                                     bash        zsh        msh
</html:span>
                    <html:span class="sh-source">-----------------------------------------------------------------------------------
</html:span>
                    <html:span class="sh-source">func.sh: no func                                      775,539  1,124,485      5,229
</html:span>
                    <html:span class="sh-source">func.sh: func                                         398,861    197,169     93,424
</html:span>
                    <html:span class="sh-source">-----------------------------------------------------------------------------------
</html:span>
                    <html:span class="sh-keyword-operator-glob">*</html:span>
                    <html:span class="sh-source"> count: number of executions per second
</html:span>
                  </html:code>
                </html:pre>
                <html:p>This particular benchmark first runs nothing (i.e. <html:code>:</html:code>) and then a function lookup and execution. Surprisingly <html:code>msh</html:code> is <html:em>slower</html:em> at doing <html:em>nothing</html:em>! I <html:code>perf</html:code>-ed a run of <html:code>msh test.sh</html:code> with just a single <html:code>:</html:code> in the file:</html:p>
                <html:iframe width="100%|" height="400px" src="/bafkrmicucn54u6lmz2u3jky3sug4nxj7yhwjf3iscpfcax7ar4fwjl7gce.svg" />
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>7</fr:month>
                      <fr:day>4</fr:day>
                    </fr:date>
                    <fr:title text="Test built-in">Test built-in</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>After making various improvements to <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> that almost halved the memory usage (e.g., removing extraneous string manipulations, adding a fast-path to the <html:code>PATH</html:code> variable for resolving programs). I was still pretty far off the performance of these other shells. I decided to have a look at <html:code>dash</html:code> which has a simple implementation.</html:p>
                    <html:iframe width="100%|" height="400px" src="/bafkrmieljhv7prkf5rovr72f22v44fdizrszrmtn46feydg6phek2sac7a.svg" />
                    <html:p>This flame graph is for the following shell script:</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="sh-entity-name-function">f</html:span>
                        <html:span class="sh-meta-function"> </html:span>
                        <html:span class="sh-punctuation-definition-arguments"><![CDATA[()]]></html:span>
                        <html:span class="sh-meta-function"> </html:span>
                        <html:span class="sh-punctuation-definition-group"><![CDATA[{]]></html:span>
                        <html:span class="sh-meta-scope-group"> </html:span>
                        <html:span class="sh-support-function-builtin">:</html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-group"> </html:span>
                        <html:span class="sh-punctuation-definition-group"><![CDATA[}]]></html:span>
                        <html:span class="sh-punctuation-definition-function">
</html:span>
                        <html:span class="sh-keyword-control">while</html:span>
                        <html:span class="sh-meta-scope-while-loop"> __count=</html:span>
                        <html:span class="sh-punctuation-definition-string-begin"><![CDATA[$((]]></html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">__count</html:span>
                        <html:span class="sh-keyword-operator-arithmetic">+</html:span>
                        <html:span class="sh-constant-numeric-integer">1</html:span>
                        <html:span class="sh-punctuation-definition-string-end"><![CDATA[))]]></html:span>
                        <html:span class="sh-meta-scope-while-loop"> </html:span>
                        <html:span class="sh-keyword-operator-list">&amp;&amp;</html:span>
                        <html:span class="sh-meta-scope-while-loop"><![CDATA[ []]></html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">__count</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-meta-scope-while-loop"><![CDATA[ -le 10000 ]]]></html:span>
                        <html:span class="sh-keyword-operator-list">;</html:span>
                        <html:span class="sh-meta-scope-while-loop"> </html:span>
                        <html:span class="sh-keyword-control">do</html:span>
                        <html:span class="sh-meta-scope-while-loop">
</html:span>
                        <html:span class="sh-meta-scope-while-loop">	f
</html:span>
                        <html:span class="sh-keyword-control">done</html:span>
                        <html:span class="sh-source">
</html:span>
                        <html:span class="sh-support-function-builtin">echo</html:span>
                        <html:span class="sh-source"> </html:span>
                        <html:span class="sh-punctuation-definition-string-begin">"</html:span>
                        <html:span class="sh-string-quoted-double">count is </html:span>
                        <html:span class="sh-punctuation-definition-variable">$</html:span>
                        <html:span class="sh-variable-other-normal">__count</html:span>
                        <html:span class="sh-punctuation-definition-string-end">"</html:span>
                        <html:span class="sh-source">
</html:span>
                      </html:code>
                    </html:pre>
                    <html:p>And here are some rudimentary benchmarks:</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="sh-source">$ </html:span>
                        <html:span class="sh-keyword-other">time</html:span>
                        <html:span class="sh-source"> -p dash test.sh
</html:span>
                        <html:span class="sh-source">count is 10001
</html:span>
                        <html:span class="sh-source">real 0.01
</html:span>
                        <html:span class="sh-source">user 0.01
</html:span>
                        <html:span class="sh-source">sys 0.00
</html:span>
                        <html:span class="sh-source">$ </html:span>
                        <html:span class="sh-keyword-other">time</html:span>
                        <html:span class="sh-source"> -p msh test.sh
</html:span>
                        <html:span class="sh-source">count is 10001
</html:span>
                        <html:span class="sh-source">real 15.53
</html:span>
                        <html:span class="sh-source">user 4.28
</html:span>
                        <html:span class="sh-source">sys 12.28
</html:span>
                      </html:code>
                    </html:pre>
                    <html:p>It turns out the massive slowdown is very simple to explain. In the <html:code>dash</html:code> flamegraph there is a <html:code>oexpr</html:code> stack; <fr:link href="https://git.kernel.org/pub/scm/utils/dash/dash.git/tree/src/bltin/test.c" type="external">which comes from the bltin/test.c</fr:link> file. <html:code>dash</html:code>, along with the other shells, does <html:em>not</html:em> spawn a process for the <html:code><![CDATA[[]]></html:code>-test command! Forcing <html:code>dash</html:code> to do use the binary <html:code><![CDATA[[]]></html:code> gives:</html:p>
                    <html:pre class="hilite">
                      <html:code>
                        <html:span class="sh-source">$ </html:span>
                        <html:span class="sh-keyword-other">time</html:span>
                        <html:span class="sh-source"> -p dash test.sh
</html:span>
                        <html:span class="sh-source">count is 10001
</html:span>
                        <html:span class="sh-source">real 7.55
</html:span>
                        <html:span class="sh-source">user 3.95
</html:span>
                        <html:span class="sh-source">sys 3.44
</html:span>
                      </html:code>
                    </html:pre>
                    <html:p>Which is much more respectable in terms of performance compared to <html:code>msh</html:code>. Plenty of work to do though!</html:p>
                  </fr:mainmatter>
                </fr:tree>
                <fr:tree show-metadata="false" numbered="false">
                  <fr:frontmatter>
                    <fr:authors>
                      <fr:author>
                        <fr:link href="https://patrick.sirref.org/Patrick Ferris/" type="external">Patrick Ferris</fr:link>
                      </fr:author>
                    </fr:authors>
                    <fr:date>
                      <fr:year>2026</fr:year>
                      <fr:month>7</fr:month>
                      <fr:day>4</fr:day>
                    </fr:date>
                    <fr:title text="Parsing arithmetic expressions">Parsing arithmetic expressions</fr:title>
                  </fr:frontmatter>
                  <fr:mainmatter>
                    <html:p>There was some obvious low-hanging fruit to help improve some of the performance differences. The first was pre-parsing arithmetic expressions. <fr:link href="github.com/colis-anr/morbig" type="external">Morbig</fr:link>, the static parser, does not implement arithmetic expression parsing; so <fr:link href="/merry/" title="Merry" uri="https://patrick.sirref.org/merry/" display-uri="merry" type="local">Merry</fr:link> does it for us. Before, it was on-demand, as we encountered an expression of the form <html:code>$((&lt;e&gt;))</html:code>. Now, it happens up front. This should improve tight-loops.</html:p>
                  </fr:mainmatter>
                </fr:tree>
              </fr:mainmatter>
            </fr:tree>
          </fr:mainmatter>
        </fr:tree>
      </fr:mainmatter>
    </fr:tree>
    <fr:tree show-metadata="false" hidden-when-empty="true">
      <fr:frontmatter>
        <fr:authors />
        <fr:title text="Contributions">Contributions</fr:title>
      </fr:frontmatter>
      <fr:mainmatter />
    </fr:tree>
  </fr:backmatter>
</fr:tree>
