<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Blog on blog.gnoack.org</title>
    <link>https://blog.gnoack.org/</link>
    <description>Recent content on blog.gnoack.org</description>
    <pubDate>Tue, 12 May 2026 06:05:53 +0000</pubDate>
    <item>
      <title>Landlock Path Walk Inversion Experiment</title>
      <link>https://blog.gnoack.org/post/landlock-path-walk-inversion-experiment</link>
      <description>&lt;ul id=&#34;toc&#34;&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#objective&#34;&gt;Objective&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#hypothesis&#34;&gt;Hypothesis&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#implementation&#34;&gt;Implementation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#measurements&#34;&gt;Measurements&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#effects-on-landlock-code-complexity&#34;&gt;Effects on Landlock code complexity&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#effects-on-performance&#34;&gt;Effects on Performance&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#noteworthy-results&#34;&gt;Noteworthy results:&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#conclusion&#34;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#full-results&#34;&gt;Full results&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#fs-bench&#34;&gt;fs_bench&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#net-bench&#34;&gt;net_bench&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#scoped-bench&#34;&gt;scoped_bench&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;objective&#34;&gt;Objective&lt;/h2&gt;&#xA;&lt;p&gt;Landlock supports multiple nested sandboxes,&#xA;so when an operation with filesystem path is attempted,&#xA;it has to check it against up to 16 nested policies.&lt;/p&gt;&#xA;&lt;p&gt;The way it currently does that is by constructing a matrix of the requested access rights per layer and checking off these matrix entries during the path walk.  This is efficient, but it also increases code complexity and I have long had a nagging doubt about whether it is worth the tradeoff.&lt;/p&gt;&#xA;&lt;p&gt;After all, the mental model for using Landlock is that each layer gets checked independently from the next.  A more &amp;ldquo;natural&amp;rdquo; way to implement this would be to:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;loop over the layers first, and&lt;/li&gt;&#xA;&lt;li&gt;then do the path walk inside (multiple times).&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;To be clear, my confidence that this would be acceptable performance-wise was always low, but then again, performance can sometimes be counterintuitive, and so it seemed like it might be worthwhile exploring.&lt;/p&gt;&#xA;&lt;p&gt;The refactoring would be a bigger change, but luckily we have a very comprehensive suite of kernel selftests in Landlock &amp;ndash; so at least for the sake of doing that experiment, it was feasible to vibe code the refactoring.  (The code is absolutely not meant to be submitted like that, but it is good enough to get performance numbers of the general idea.)&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The experiment failed&lt;/strong&gt; &amp;ndash; it worsened performance noticeably.&#xA;I&amp;rsquo;m publishing it anyway, so that others don&amp;rsquo;t have to attempt the same.&lt;/p&gt;&#xA;&lt;p&gt;I also found it amazing that I can play with the performance aspects of such a large refactoring without implementing it myself. ✨  LLMs are underused for such experiments.&lt;/p&gt;&#xA;&lt;h2 id=&#34;hypothesis&#34;&gt;Hypothesis&lt;/h2&gt;&#xA;&lt;p&gt;The (highly speculative) hypothesis of this experiment is:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;By refactoring the code to loop over the layers first, and doing the path walk inside (multiple times),&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;the code becomes simpler to reason about,&lt;/li&gt;&#xA;&lt;li&gt;and keeps reasonable performance.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;implementation&#34;&gt;Implementation&lt;/h2&gt;&#xA;&lt;p&gt;I gave the refactoring task to an LLM agent.  &lt;strong&gt;This code is not production ready and entirely unreviewed&lt;/strong&gt;, but it does pass the very comprehensive Landlock test suite, which remained unmodified, so I have good confidence that the relevant aspects are retained.&lt;/p&gt;&#xA;&lt;h2 id=&#34;measurements&#34;&gt;Measurements&lt;/h2&gt;&#xA;&lt;h3 id=&#34;effects-on-landlock-code-complexity&#34;&gt;Effects on Landlock code complexity&lt;/h3&gt;&#xA;&lt;p&gt;The measurement was done using:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;wc -l security/landlock/*.[ch]&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The lines of code in Landlock went down from 7245 (2006 in &lt;code&gt;fs.c&lt;/code&gt;) to 6815 (1726 in &lt;code&gt;fs.c&lt;/code&gt;) lines.&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-0&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:406px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 406.98 200.28&#34;&gt;&#xA;&lt;path d=&#34;M1.5,54.78L233.317,54.78L233.317,25.98L1.5,25.98Z&#34;  style=&#34;fill:rgb(250,128,114);stroke-width:1.5;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;230.062&#34; y=&#34;40.38&#34; text-anchor=&#34;end&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;6815&lt;/text&gt;&#xA;&lt;text x=&#34;237.637&#34; y=&#34;40.38&#34; text-anchor=&#34;start&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;after refactoring&lt;/text&gt;&#xA;&lt;path d=&#34;M1.5,83.58L247.944,83.58L247.944,54.78L1.5,54.78Z&#34;  style=&#34;fill:rgb(135,206,235);stroke-width:1.5;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;244.689&#34; y=&#34;69.18&#34; text-anchor=&#34;end&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;7245&lt;/text&gt;&#xA;&lt;text x=&#34;252.264&#34; y=&#34;69.18&#34; text-anchor=&#34;start&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;before refactoring&lt;/text&gt;&#xA;&lt;path d=&#34;M1.5,169.98L60.2112,169.98L60.2112,141.18L1.5,141.18Z&#34;  style=&#34;fill:rgb(250,128,114);stroke-width:1.5;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.9562&#34; y=&#34;155.58&#34; text-anchor=&#34;end&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;1726&lt;/text&gt;&#xA;&lt;text x=&#34;64.5312&#34; y=&#34;155.58&#34; text-anchor=&#34;start&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;after refactoring&lt;/text&gt;&#xA;&lt;path d=&#34;M1.5,198.78L69.7356,198.78L69.7356,169.98L1.5,169.98Z&#34;  style=&#34;fill:rgb(135,206,235);stroke-width:1.5;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;66.4806&#34; y=&#34;184.38&#34; text-anchor=&#34;end&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;2006&lt;/text&gt;&#xA;&lt;text x=&#34;74.0556&#34; y=&#34;184.38&#34; text-anchor=&#34;start&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;before refactoring&lt;/text&gt;&#xA;&lt;text x=&#34;1.5&#34; y=&#34;11.58&#34; text-anchor=&#34;start&#34; font-weight=&#34;bold&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Lines of Code in security/landlock/*.[ch]&lt;/text&gt;&#xA;&lt;text x=&#34;1.5&#34; y=&#34;126.78&#34; text-anchor=&#34;start&#34; font-weight=&#34;bold&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Lines of Code in security/landlock/fs.c&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;The usual caveats apply:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Lines of code are a poor measurement of actual complexity.&lt;/li&gt;&#xA;&lt;li&gt;It might have deleted some KUnit tests along the way.&#xA;Whether that is legitimate due to simplification is a subjective question.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;But it&amp;rsquo;s at least an indication that it does indeed simplify the code.&lt;/p&gt;&#xA;&lt;p&gt;The full refactoring and benchmarking code are kept on a Git branch at &lt;a href=&#34;https://github.com/gnoack/linux/tree/landlock-invert&#34;&gt;https://github.com/gnoack/linux/tree/landlock-invert&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;effects-on-performance&#34;&gt;Effects on Performance&lt;/h3&gt;&#xA;&lt;p&gt;This is measured using an improved version of &lt;code&gt;fs_bench&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;fs_bench&lt;/code&gt; benchmarks how long it takes to check Landlock policies during &lt;code&gt;open()&lt;/code&gt;, and it does so in an intentionally very-bad-case scenario, namely one where the opened file is in a deeply nested filesystem location from the root, and where the operation is in the end not permitted, so that Landlock has to walk the path all the way up to the root.&lt;/p&gt;&#xA;&lt;p&gt;This is chosen to amplify the effect and to make it more measurable.&#xA;In more practical scenarios,&#xA;the number of nested Landlock domains is likely to be low (e.g., 1),&#xA;and the number of nested directories is also low (e.g. &amp;lt;10).&lt;/p&gt;&#xA;&lt;p&gt;The resulting performance graphs show that independent of the number of nested directories, adding more nested Landlock sandboxes does not scale well after the refactoring attempt.&lt;/p&gt;&#xA;&lt;style&gt;svg { display: block; margin: auto; }&lt;/style&gt;&#xA;&lt;div style=&#34;display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: 1em; max-width: 1000px;&#34;&gt;&#xA;  &lt;div&gt;&#xA;&lt;svg &#xA; width=&#34;480&#34; height=&#34;320&#34;&#xA; viewBox=&#34;0 0 480 320&#34;&#xA; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA; xmlns:xlink=&#34;http://www.w3.org/1999/xlink&#34;&#xA;&gt;&#xA;&lt;title&gt;Gnuplot&lt;/title&gt;&#xA;&lt;desc&gt;Produced by GNUPLOT 6.0 patchlevel 4 &lt;/desc&gt;&#xA;&lt;g id=&#34;gnuplot_canvas&#34;&gt;&#xA;&lt;rect x=&#34;0&#34; y=&#34;0&#34; width=&#34;480&#34; height=&#34;320&#34; fill=&#34;none&#34;/&gt;&#xA;&lt;defs&gt;&#xA;&#x9;&lt;circle id=&#39;gpDot&#39; r=&#39;0.5&#39; stroke-width=&#39;0.5&#39; stroke=&#39;currentColor&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt0&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,0 h2 M0,-1 v2&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt1&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,-1 L1,1 M1,-1 L-1,1&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt2&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,0 L1,0 M0,-1 L0,1 M-1,-1 L1,1 M-1,1 L1,-1&#39;/&gt;&#xA;&#x9;&lt;rect id=&#39;gpPt3&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; x=&#39;-1&#39; y=&#39;-1&#39; width=&#39;2&#39; height=&#39;2&#39;/&gt;&#xA;&#x9;&lt;rect id=&#39;gpPt4&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; fill=&#39;currentColor&#39; x=&#39;-1&#39; y=&#39;-1&#39; width=&#39;2&#39; height=&#39;2&#39;/&gt;&#xA;&#x9;&lt;circle id=&#39;gpPt5&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; cx=&#39;0&#39; cy=&#39;0&#39; r=&#39;1&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt5&#39; id=&#39;gpPt6&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt7&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M0,-1.33 L-1.33,0.67 L1.33,0.67 z&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt7&#39; id=&#39;gpPt8&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt7&#39; id=&#39;gpPt9&#39; stroke=&#39;currentColor&#39; transform=&#39;rotate(180)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt9&#39; id=&#39;gpPt10&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt3&#39; id=&#39;gpPt11&#39; stroke=&#39;currentColor&#39; transform=&#39;rotate(45)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt11&#39; id=&#39;gpPt12&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt13&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M0,1.330 L1.265,0.411 L0.782,-1.067 L-0.782,-1.076 L-1.265,0.411 z&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt13&#39; id=&#39;gpPt14&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;filter id=&#39;textbox&#39; filterUnits=&#39;objectBoundingBox&#39; x=&#39;0&#39; y=&#39;0&#39; height=&#39;1&#39; width=&#39;1&#39;&gt;&#xA;&#x9;  &lt;feFlood flood-color=&#39;white&#39; flood-opacity=&#39;1&#39; result=&#39;bgnd&#39;/&gt;&#xA;&#x9;  &lt;feComposite in=&#39;SourceGraphic&#39; in2=&#39;bgnd&#39; operator=&#39;atop&#39;/&gt;&#xA;&#x9;&lt;/filter&gt;&#xA;&#x9;&lt;filter id=&#39;greybox&#39; filterUnits=&#39;objectBoundingBox&#39; x=&#39;0&#39; y=&#39;0&#39; height=&#39;1&#39; width=&#39;1&#39;&gt;&#xA;&#x9;  &lt;feFlood flood-color=&#39;lightgrey&#39; flood-opacity=&#39;1&#39; result=&#39;grey&#39;/&gt;&#xA;&#x9;  &lt;feComposite in=&#39;SourceGraphic&#39; in2=&#39;grey&#39; operator=&#39;atop&#39;/&gt;&#xA;&#x9;&lt;/filter&gt;&#xA;&lt;/defs&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;white&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M64.14,262.40 L454.82,262.40  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M64.14,262.40 L73.14,262.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(55.75,266.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 0&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M64.14,232.63 L454.82,232.63  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M64.14,232.63 L73.14,232.63  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(55.75,236.53)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 10&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M64.14,202.86 L454.82,202.86  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M64.14,202.86 L73.14,202.86  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(55.75,206.76)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 20&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M64.14,173.09 L454.82,173.09  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M64.14,173.09 L73.14,173.09  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(55.75,176.99)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 30&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M64.14,143.32 L454.82,143.32  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M64.14,143.32 L73.14,143.32  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(55.75,147.22)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 40&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M64.14,113.55 L454.82,113.55  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M64.14,113.55 L73.14,113.55  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(55.75,117.45)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 50&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M64.14,83.78 L72.53,83.78 M182.21,83.78 L454.82,83.78  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M64.14,83.78 L73.14,83.78  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(55.75,87.68)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 60&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M64.14,54.01 L454.82,54.01  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M64.14,54.01 L73.14,54.01  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(55.75,57.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 70&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M64.14,262.40 L64.14,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(64.14,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 1&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M161.81,262.40 L161.81,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(161.81,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 2&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M259.48,262.40 L259.48,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(259.48,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 4&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M357.15,262.40 L357.15,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(357.15,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 8&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M64.14,54.01 L64.14,262.40 L454.82,262.40 M454.82,54.01 M64.14,54.01  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;g id=&#34;gnuplot_plot_1&#34; &gt;&lt;title&gt;before&lt;/title&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;white&#34; stroke=&#34;black&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(122.87,75.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;before&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb( 59, 130, 246)&#39;  d=&#39;M131.26,72.01 L173.82,72.01 M64.14,244.54 L161.81,244.54 L259.48,241.56 L357.15,241.56 L454.82,238.58  &#39;/&gt;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(64.14,244.54) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(161.81,244.54) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(259.48,241.56) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(357.15,241.56) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(454.82,238.58) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(152.54,72.01) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&#x9;&lt;g id=&#34;gnuplot_plot_2&#34; &gt;&lt;title&gt;after&lt;/title&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(122.87,93.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;after&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(239,  68,  68)&#39;  d=&#39;M131.26,90.01 L173.82,90.01 M64.14,244.54 L161.81,235.61 L259.48,217.74 L357.15,173.09 L454.82,80.80  &#39;/&gt;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(64.14,244.54) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(161.81,235.61) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(259.48,217.74) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(357.15,173.09) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(454.82,80.80) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(152.54,90.01) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M64.14,54.01 L64.14,262.40 L454.82,262.40 M454.82,54.01 M64.14,54.01  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(19.18,158.21) rotate(270.00)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;System clocks&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(259.48,311.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;Landlock layers&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(259.48,30.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;fs_bench, depth = 10&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;/svg&gt;&#xA;  &lt;/div&gt;&#xA;  &lt;div&gt;&#xA;&lt;svg &#xA; width=&#34;480&#34; height=&#34;320&#34;&#xA; viewBox=&#34;0 0 480 320&#34;&#xA; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA; xmlns:xlink=&#34;http://www.w3.org/1999/xlink&#34;&#xA;&gt;&#xA;&lt;title&gt;Gnuplot&lt;/title&gt;&#xA;&lt;desc&gt;Produced by GNUPLOT 6.0 patchlevel 4 &lt;/desc&gt;&#xA;&lt;g id=&#34;gnuplot_canvas&#34;&gt;&#xA;&lt;rect x=&#34;0&#34; y=&#34;0&#34; width=&#34;480&#34; height=&#34;320&#34; fill=&#34;none&#34;/&gt;&#xA;&lt;defs&gt;&#xA;&#x9;&lt;circle id=&#39;gpDot&#39; r=&#39;0.5&#39; stroke-width=&#39;0.5&#39; stroke=&#39;currentColor&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt0&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,0 h2 M0,-1 v2&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt1&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,-1 L1,1 M1,-1 L-1,1&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt2&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,0 L1,0 M0,-1 L0,1 M-1,-1 L1,1 M-1,1 L1,-1&#39;/&gt;&#xA;&#x9;&lt;rect id=&#39;gpPt3&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; x=&#39;-1&#39; y=&#39;-1&#39; width=&#39;2&#39; height=&#39;2&#39;/&gt;&#xA;&#x9;&lt;rect id=&#39;gpPt4&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; fill=&#39;currentColor&#39; x=&#39;-1&#39; y=&#39;-1&#39; width=&#39;2&#39; height=&#39;2&#39;/&gt;&#xA;&#x9;&lt;circle id=&#39;gpPt5&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; cx=&#39;0&#39; cy=&#39;0&#39; r=&#39;1&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt5&#39; id=&#39;gpPt6&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt7&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M0,-1.33 L-1.33,0.67 L1.33,0.67 z&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt7&#39; id=&#39;gpPt8&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt7&#39; id=&#39;gpPt9&#39; stroke=&#39;currentColor&#39; transform=&#39;rotate(180)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt9&#39; id=&#39;gpPt10&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt3&#39; id=&#39;gpPt11&#39; stroke=&#39;currentColor&#39; transform=&#39;rotate(45)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt11&#39; id=&#39;gpPt12&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt13&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M0,1.330 L1.265,0.411 L0.782,-1.067 L-0.782,-1.076 L-1.265,0.411 z&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt13&#39; id=&#39;gpPt14&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;filter id=&#39;textbox&#39; filterUnits=&#39;objectBoundingBox&#39; x=&#39;0&#39; y=&#39;0&#39; height=&#39;1&#39; width=&#39;1&#39;&gt;&#xA;&#x9;  &lt;feFlood flood-color=&#39;white&#39; flood-opacity=&#39;1&#39; result=&#39;bgnd&#39;/&gt;&#xA;&#x9;  &lt;feComposite in=&#39;SourceGraphic&#39; in2=&#39;bgnd&#39; operator=&#39;atop&#39;/&gt;&#xA;&#x9;&lt;/filter&gt;&#xA;&#x9;&lt;filter id=&#39;greybox&#39; filterUnits=&#39;objectBoundingBox&#39; x=&#39;0&#39; y=&#39;0&#39; height=&#39;1&#39; width=&#39;1&#39;&gt;&#xA;&#x9;  &lt;feFlood flood-color=&#39;lightgrey&#39; flood-opacity=&#39;1&#39; result=&#39;grey&#39;/&gt;&#xA;&#x9;  &lt;feComposite in=&#39;SourceGraphic&#39; in2=&#39;grey&#39; operator=&#39;atop&#39;/&gt;&#xA;&#x9;&lt;/filter&gt;&#xA;&lt;/defs&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;white&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M72.53,262.40 L454.82,262.40  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M72.53,262.40 L81.53,262.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(64.14,266.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 0&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M72.53,232.63 L454.82,232.63  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M72.53,232.63 L81.53,232.63  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(64.14,236.53)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 100&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M72.53,202.86 L454.82,202.86  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M72.53,202.86 L81.53,202.86  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(64.14,206.76)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 200&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M72.53,173.09 L454.82,173.09  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M72.53,173.09 L81.53,173.09  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(64.14,176.99)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 300&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M72.53,143.32 L454.82,143.32  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M72.53,143.32 L81.53,143.32  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(64.14,147.22)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 400&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M72.53,113.55 L454.82,113.55  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M72.53,113.55 L81.53,113.55  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(64.14,117.45)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 500&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M72.53,83.78 L80.92,83.78 M190.60,83.78 L454.82,83.78  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M72.53,83.78 L81.53,83.78  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(64.14,87.68)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 600&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M72.53,54.01 L454.82,54.01  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M72.53,54.01 L81.53,54.01  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(64.14,57.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 700&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M72.53,262.40 L72.53,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(72.53,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 1&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M168.10,262.40 L168.10,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(168.10,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 2&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M263.68,262.40 L263.68,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(263.68,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 4&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M359.25,262.40 L359.25,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(359.25,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 8&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M72.53,54.01 L72.53,262.40 L454.82,262.40 M454.82,54.01 M72.53,54.01  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;g id=&#34;gnuplot_plot_1&#34; &gt;&lt;title&gt;before&lt;/title&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;white&#34; stroke=&#34;black&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(131.26,75.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;before&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb( 59, 130, 246)&#39;  d=&#39;M139.65,72.01 L182.21,72.01 M72.53,252.87 L168.10,252.28 L263.68,251.39 L359.25,250.49 L454.82,248.41  &#39;/&gt;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(72.53,252.87) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(168.10,252.28) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(263.68,251.39) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(359.25,250.49) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(454.82,248.41) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(160.93,72.01) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&#x9;&lt;g id=&#34;gnuplot_plot_2&#34; &gt;&lt;title&gt;after&lt;/title&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(131.26,93.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;after&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(239,  68,  68)&#39;  d=&#39;M139.65,90.01 L182.21,90.01 M72.53,253.77 L168.10,244.84 L263.68,225.78 L359.25,173.39 L454.82,60.56  &#39;/&gt;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(72.53,253.77) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(168.10,244.84) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(263.68,225.78) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(359.25,173.39) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(454.82,60.56) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(160.93,90.01) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M72.53,54.01 L72.53,262.40 L454.82,262.40 M454.82,54.01 M72.53,54.01  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(19.18,158.21) rotate(270.00)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;System clocks&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(263.67,311.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;Landlock layers&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(263.67,30.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;fs_bench, depth = 100&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;/svg&gt;&#xA;  &lt;/div&gt;&#xA;  &lt;div&gt;&#xA;&lt;svg &#xA; width=&#34;480&#34; height=&#34;320&#34;&#xA; viewBox=&#34;0 0 480 320&#34;&#xA; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA; xmlns:xlink=&#34;http://www.w3.org/1999/xlink&#34;&#xA;&gt;&#xA;&lt;title&gt;Gnuplot&lt;/title&gt;&#xA;&lt;desc&gt;Produced by GNUPLOT 6.0 patchlevel 4 &lt;/desc&gt;&#xA;&lt;g id=&#34;gnuplot_canvas&#34;&gt;&#xA;&lt;rect x=&#34;0&#34; y=&#34;0&#34; width=&#34;480&#34; height=&#34;320&#34; fill=&#34;none&#34;/&gt;&#xA;&lt;defs&gt;&#xA;&#x9;&lt;circle id=&#39;gpDot&#39; r=&#39;0.5&#39; stroke-width=&#39;0.5&#39; stroke=&#39;currentColor&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt0&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,0 h2 M0,-1 v2&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt1&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,-1 L1,1 M1,-1 L-1,1&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt2&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,0 L1,0 M0,-1 L0,1 M-1,-1 L1,1 M-1,1 L1,-1&#39;/&gt;&#xA;&#x9;&lt;rect id=&#39;gpPt3&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; x=&#39;-1&#39; y=&#39;-1&#39; width=&#39;2&#39; height=&#39;2&#39;/&gt;&#xA;&#x9;&lt;rect id=&#39;gpPt4&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; fill=&#39;currentColor&#39; x=&#39;-1&#39; y=&#39;-1&#39; width=&#39;2&#39; height=&#39;2&#39;/&gt;&#xA;&#x9;&lt;circle id=&#39;gpPt5&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; cx=&#39;0&#39; cy=&#39;0&#39; r=&#39;1&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt5&#39; id=&#39;gpPt6&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt7&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M0,-1.33 L-1.33,0.67 L1.33,0.67 z&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt7&#39; id=&#39;gpPt8&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt7&#39; id=&#39;gpPt9&#39; stroke=&#39;currentColor&#39; transform=&#39;rotate(180)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt9&#39; id=&#39;gpPt10&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt3&#39; id=&#39;gpPt11&#39; stroke=&#39;currentColor&#39; transform=&#39;rotate(45)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt11&#39; id=&#39;gpPt12&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt13&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M0,1.330 L1.265,0.411 L0.782,-1.067 L-0.782,-1.076 L-1.265,0.411 z&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt13&#39; id=&#39;gpPt14&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;filter id=&#39;textbox&#39; filterUnits=&#39;objectBoundingBox&#39; x=&#39;0&#39; y=&#39;0&#39; height=&#39;1&#39; width=&#39;1&#39;&gt;&#xA;&#x9;  &lt;feFlood flood-color=&#39;white&#39; flood-opacity=&#39;1&#39; result=&#39;bgnd&#39;/&gt;&#xA;&#x9;  &lt;feComposite in=&#39;SourceGraphic&#39; in2=&#39;bgnd&#39; operator=&#39;atop&#39;/&gt;&#xA;&#x9;&lt;/filter&gt;&#xA;&#x9;&lt;filter id=&#39;greybox&#39; filterUnits=&#39;objectBoundingBox&#39; x=&#39;0&#39; y=&#39;0&#39; height=&#39;1&#39; width=&#39;1&#39;&gt;&#xA;&#x9;  &lt;feFlood flood-color=&#39;lightgrey&#39; flood-opacity=&#39;1&#39; result=&#39;grey&#39;/&gt;&#xA;&#x9;  &lt;feComposite in=&#39;SourceGraphic&#39; in2=&#39;grey&#39; operator=&#39;atop&#39;/&gt;&#xA;&#x9;&lt;/filter&gt;&#xA;&lt;/defs&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;white&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M80.92,262.40 L454.82,262.40  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,262.40 L89.92,262.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(72.53,266.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 0&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M80.92,239.25 L454.82,239.25  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,239.25 L89.92,239.25  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(72.53,243.15)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 1000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M80.92,216.09 L454.82,216.09  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,216.09 L89.92,216.09  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(72.53,219.99)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 2000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M80.92,192.94 L454.82,192.94  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,192.94 L89.92,192.94  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(72.53,196.84)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 3000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M80.92,169.78 L454.82,169.78  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,169.78 L89.92,169.78  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(72.53,173.68)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 4000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M80.92,146.63 L454.82,146.63  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,146.63 L89.92,146.63  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(72.53,150.53)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 5000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M80.92,123.47 L454.82,123.47  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,123.47 L89.92,123.47  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(72.53,127.37)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 6000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M80.92,100.32 L454.82,100.32  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,100.32 L89.92,100.32  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(72.53,104.22)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 7000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M80.92,77.16 L89.31,77.16 M198.99,77.16 L454.82,77.16  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,77.16 L89.92,77.16  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(72.53,81.06)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 8000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M80.92,54.01 L454.82,54.01  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,54.01 L89.92,54.01  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(72.53,57.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 9000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,262.40 L80.92,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(80.92,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 1&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M174.40,262.40 L174.40,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(174.40,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 2&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M267.87,262.40 L267.87,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(267.87,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 4&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M361.35,262.40 L361.35,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(361.35,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 8&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,54.01 L80.92,262.40 L454.82,262.40 M454.82,54.01 M80.92,54.01  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;g id=&#34;gnuplot_plot_1&#34; &gt;&lt;title&gt;before&lt;/title&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;white&#34; stroke=&#34;black&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(139.65,75.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;before&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb( 59, 130, 246)&#39;  d=&#39;M148.04,72.01 L190.60,72.01 M80.92,251.54 L174.40,251.91 L267.87,250.68 L361.35,250.13 L454.82,248.62  &#39;/&gt;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(80.92,251.54) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(174.40,251.91) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(267.87,250.68) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(361.35,250.13) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(454.82,248.62) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(169.32,72.01) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&#x9;&lt;g id=&#34;gnuplot_plot_2&#34; &gt;&lt;title&gt;after&lt;/title&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(139.65,93.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;after&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(239,  68,  68)&#39;  d=&#39;M148.04,90.01 L190.60,90.01 M80.92,253.51 L174.40,245.47 L267.87,219.17 L361.35,173.81 L454.82,61.33  &#39;/&gt;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(80.92,253.51) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(174.40,245.47) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(267.87,219.17) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(361.35,173.81) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(454.82,61.33) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(169.32,90.01) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M80.92,54.01 L80.92,262.40 L454.82,262.40 M454.82,54.01 M80.92,54.01  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(19.18,158.21) rotate(270.00)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;System clocks&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(267.87,311.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;Landlock layers&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(267.87,30.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;fs_bench, depth = 1000&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;/svg&gt;&#xA;  &lt;/div&gt;&#xA;  &lt;div&gt;&#xA;&lt;svg &#xA; width=&#34;480&#34; height=&#34;320&#34;&#xA; viewBox=&#34;0 0 480 320&#34;&#xA; xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA; xmlns:xlink=&#34;http://www.w3.org/1999/xlink&#34;&#xA;&gt;&#xA;&lt;title&gt;Gnuplot&lt;/title&gt;&#xA;&lt;desc&gt;Produced by GNUPLOT 6.0 patchlevel 4 &lt;/desc&gt;&#xA;&lt;g id=&#34;gnuplot_canvas&#34;&gt;&#xA;&lt;rect x=&#34;0&#34; y=&#34;0&#34; width=&#34;480&#34; height=&#34;320&#34; fill=&#34;none&#34;/&gt;&#xA;&lt;defs&gt;&#xA;&#x9;&lt;circle id=&#39;gpDot&#39; r=&#39;0.5&#39; stroke-width=&#39;0.5&#39; stroke=&#39;currentColor&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt0&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,0 h2 M0,-1 v2&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt1&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,-1 L1,1 M1,-1 L-1,1&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt2&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M-1,0 L1,0 M0,-1 L0,1 M-1,-1 L1,1 M-1,1 L1,-1&#39;/&gt;&#xA;&#x9;&lt;rect id=&#39;gpPt3&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; x=&#39;-1&#39; y=&#39;-1&#39; width=&#39;2&#39; height=&#39;2&#39;/&gt;&#xA;&#x9;&lt;rect id=&#39;gpPt4&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; fill=&#39;currentColor&#39; x=&#39;-1&#39; y=&#39;-1&#39; width=&#39;2&#39; height=&#39;2&#39;/&gt;&#xA;&#x9;&lt;circle id=&#39;gpPt5&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; cx=&#39;0&#39; cy=&#39;0&#39; r=&#39;1&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt5&#39; id=&#39;gpPt6&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt7&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M0,-1.33 L-1.33,0.67 L1.33,0.67 z&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt7&#39; id=&#39;gpPt8&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt7&#39; id=&#39;gpPt9&#39; stroke=&#39;currentColor&#39; transform=&#39;rotate(180)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt9&#39; id=&#39;gpPt10&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt3&#39; id=&#39;gpPt11&#39; stroke=&#39;currentColor&#39; transform=&#39;rotate(45)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt11&#39; id=&#39;gpPt12&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;path id=&#39;gpPt13&#39; stroke-width=&#39;0.222&#39; stroke=&#39;currentColor&#39; d=&#39;M0,1.330 L1.265,0.411 L0.782,-1.067 L-0.782,-1.076 L-1.265,0.411 z&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt13&#39; id=&#39;gpPt14&#39; fill=&#39;currentColor&#39; stroke=&#39;none&#39;/&gt;&#xA;&#x9;&lt;filter id=&#39;textbox&#39; filterUnits=&#39;objectBoundingBox&#39; x=&#39;0&#39; y=&#39;0&#39; height=&#39;1&#39; width=&#39;1&#39;&gt;&#xA;&#x9;  &lt;feFlood flood-color=&#39;white&#39; flood-opacity=&#39;1&#39; result=&#39;bgnd&#39;/&gt;&#xA;&#x9;  &lt;feComposite in=&#39;SourceGraphic&#39; in2=&#39;bgnd&#39; operator=&#39;atop&#39;/&gt;&#xA;&#x9;&lt;/filter&gt;&#xA;&#x9;&lt;filter id=&#39;greybox&#39; filterUnits=&#39;objectBoundingBox&#39; x=&#39;0&#39; y=&#39;0&#39; height=&#39;1&#39; width=&#39;1&#39;&gt;&#xA;&#x9;  &lt;feFlood flood-color=&#39;lightgrey&#39; flood-opacity=&#39;1&#39; result=&#39;grey&#39;/&gt;&#xA;&#x9;  &lt;feComposite in=&#39;SourceGraphic&#39; in2=&#39;grey&#39; operator=&#39;atop&#39;/&gt;&#xA;&#x9;&lt;/filter&gt;&#xA;&lt;/defs&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;white&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M97.70,262.40 L454.82,262.40  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,262.40 L106.70,262.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(89.31,266.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 0&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M97.70,241.56 L454.82,241.56  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,241.56 L106.70,241.56  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(89.31,245.46)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 10000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M97.70,220.72 L454.82,220.72  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,220.72 L106.70,220.72  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(89.31,224.62)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 20000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M97.70,199.88 L454.82,199.88  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,199.88 L106.70,199.88  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(89.31,203.78)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 30000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M97.70,179.04 L454.82,179.04  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,179.04 L106.70,179.04  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(89.31,182.94)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 40000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M97.70,158.20 L454.82,158.20  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,158.20 L106.70,158.20  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(89.31,162.10)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 50000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M97.70,137.37 L454.82,137.37  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,137.37 L106.70,137.37  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(89.31,141.27)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 60000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M97.70,116.53 L454.82,116.53  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,116.53 L106.70,116.53  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(89.31,120.43)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 70000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M97.70,95.69 L106.09,95.69 M215.77,95.69 L454.82,95.69  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,95.69 L106.70,95.69  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(89.31,99.59)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 80000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M97.70,74.85 L106.09,74.85 M215.77,74.85 L454.82,74.85  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,74.85 L106.70,74.85  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(89.31,78.75)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 90000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;0.50&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(221, 221, 221)&#39; stroke-dasharray=&#39;2,4&#39; class=&#34;gridline&#34;  d=&#39;M97.70,54.01 L454.82,54.01  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;gray&#34; stroke=&#34;rgb(221, 221, 221)&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,54.01 L106.70,54.01  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(89.31,57.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 100000&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,262.40 L97.70,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(97.70,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 1&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M186.98,262.40 L186.98,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(186.98,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 2&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M276.26,262.40 L276.26,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(276.26,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 4&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M365.54,262.40 L365.54,253.40  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(365.54,284.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt; 8&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,54.01 L97.70,262.40 L454.82,262.40 M454.82,54.01 M97.70,54.01  &#39;/&gt;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;g id=&#34;gnuplot_plot_1&#34; &gt;&lt;title&gt;before&lt;/title&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;white&#34; stroke=&#34;black&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(156.43,75.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;before&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb( 59, 130, 246)&#39;  d=&#39;M164.82,72.01 L207.38,72.01 M97.70,249.72 L186.98,249.75 L276.26,249.31 L365.54,248.95 L454.82,247.80  &#39;/&gt;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(97.70,249.72) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(186.98,249.75) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(276.26,249.31) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(365.54,248.95) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(454.82,247.80) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(186.10,72.01) scale(3.60)&#39; color=&#39;rgb( 59, 130, 246)&#39;/&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&#x9;&lt;g id=&#34;gnuplot_plot_2&#34; &gt;&lt;title&gt;after&lt;/title&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(156.43,93.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;end&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;after&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;rgb(239,  68,  68)&#39;  d=&#39;M164.82,90.01 L207.38,90.01 M97.70,252.66 L186.98,242.60 L276.26,220.86 L365.54,169.70 L454.82,55.05  &#39;/&gt;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(97.70,252.66) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(186.98,242.60) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(276.26,220.86) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(365.54,169.70) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(454.82,55.05) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&#x9;&lt;use xlink:href=&#39;#gpPt6&#39; transform=&#39;translate(186.10,90.01) scale(3.60)&#39; color=&#39;rgb(239,  68,  68)&#39;/&gt;&#xA;&lt;/g&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;black&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;path stroke=&#39;black&#39;  d=&#39;M97.70,54.01 L97.70,262.40 L454.82,262.40 M454.82,54.01 M97.70,54.01  &#39;/&gt;&#x9;&lt;g transform=&#34;translate(19.18,158.21) rotate(270.00)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;System clocks&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(276.26,311.30)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;&lt;tspan font-family=&#34;system-ui&#34; &gt;Landlock layers&lt;/tspan&gt;&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&#x9;&lt;g transform=&#34;translate(276.26,30.91)&#34; stroke=&#34;none&#34; fill=&#34;black&#34; font-family=&#34;system-ui&#34; font-size=&#34;12.00&#34;  text-anchor=&#34;middle&#34;&gt;&#xA;&#x9;&#x9;&lt;text&gt;fs_bench, depth = 10000&lt;/text&gt;&#xA;&#x9;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;g fill=&#34;none&#34; color=&#34;black&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;1.00&#34; stroke-linecap=&#34;butt&#34; stroke-linejoin=&#34;miter&#34;&gt;&#xA;&lt;/g&gt;&#xA;&lt;/g&gt;&#xA;&lt;/svg&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;h4 id=&#34;noteworthy-results&#34;&gt;Noteworthy results:&lt;/h4&gt;&#xA;&lt;p&gt;The refactoring attempt does not scale well when adding more Landlock sandboxes.&lt;/p&gt;&#xA;&lt;p&gt;Even with a small and more realistic directory nesting depth of only 10, the &lt;strong&gt;performance degradation of doing the path walk twice when adding a second Landlock layer is clearly visible&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;In the case of &lt;strong&gt;only one layer, the performance improved slightly&lt;/strong&gt;, presumably because the code is just simpler now, but this only becomes properly measurable at a high number of nested directories.  In the more realistic scenario with only 10 directories, the effect is not measurable with the clock resolution.&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;Directory nesting depth&lt;/th&gt;&#xA;&lt;th&gt;before refactoring (clocks)&lt;/th&gt;&#xA;&lt;th&gt;after refactoring (clocks)&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;10&lt;/td&gt;&#xA;&lt;td&gt;6&lt;/td&gt;&#xA;&lt;td&gt;6&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;100&lt;/td&gt;&#xA;&lt;td&gt;32&lt;/td&gt;&#xA;&lt;td&gt;29 (90%)&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;1000&lt;/td&gt;&#xA;&lt;td&gt;469&lt;/td&gt;&#xA;&lt;td&gt;384 (82%)&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;10000&lt;/td&gt;&#xA;&lt;td&gt;6087&lt;/td&gt;&#xA;&lt;td&gt;4676 (77%)&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;&#xA;&lt;p&gt;❌ The refactoring was a bad idea and it reduces performance.&lt;/p&gt;&#xA;&lt;p&gt;The current path walk implementation with the layer matrix performs better,&#xA;even in scenarios with small numbers.&lt;/p&gt;&#xA;&lt;h2 id=&#34;full-results&#34;&gt;Full results&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Kernel A: &lt;strong&gt;before&lt;/strong&gt; (&lt;code&gt;benchmarking/bzImage-before&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;Kernel B: &lt;strong&gt;after&lt;/strong&gt; (&lt;code&gt;benchmarking/bzImage-after&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Values are &amp;ldquo;System&amp;rdquo; clock ticks from times(2) (smaller = faster).&lt;/p&gt;&#xA;&lt;h3 id=&#34;fs-bench&#34;&gt;fs_bench&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;Scenario&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;before&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;after&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;Δ (B − A)&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;B/A&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10000, baseline&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;6&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;6&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;0&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.000&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10000, 1 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;6087&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;4676&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;-1411&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;0.768&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10000, 2 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;6072&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;9502&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;3430&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.565&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10000, 4 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;6281&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;19933&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;13652&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;3.174&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10000, 8 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;6452&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;44484&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;38032&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;6.895&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10000, 16 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;7007&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;99500&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;92493&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;14.200&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=1000, baseline&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;4&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;4&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;0&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.000&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=1000, 1 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;469&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;384&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;-85&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;0.819&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=1000, 2 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;453&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;731&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;278&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.614&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=1000, 4 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;506&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1867&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1361&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;3.690&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=1000, 8 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;530&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;3826&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;3296&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;7.219&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=1000, 16 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;595&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;8684&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;8089&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;14.595&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=100, baseline&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;3&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;4&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.333&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=100, 1 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;32&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;29&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;-3&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;0.906&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=100, 2 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;34&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;59&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;25&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.735&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=100, 4 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;37&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;123&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;86&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;3.324&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=100, 8 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;40&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;299&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;259&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;7.475&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=100, 16 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;47&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;678&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;631&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;14.426&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10, baseline&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;4&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;3&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;-1&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;0.750&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10, 1 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;6&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;6&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;0&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.000&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10, 2 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;6&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;9&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;3&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.500&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10, 4 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;7&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;15&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;8&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;2.143&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10, 8 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;7&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;30&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;23&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;4.286&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;fs_bench / depth=10, 16 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;8&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;61&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;53&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;7.625&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;net-bench&#34;&gt;net_bench&lt;/h3&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;Scenario&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;before&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;after&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;Δ (B − A)&lt;/th&gt;&#xA;&lt;th style=&#34;text-align:right&#34;&gt;B/A&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;net_bench / baseline&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;49&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;49&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;0&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.000&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;net_bench / 2 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;9&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;9&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;0&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.000&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;net_bench / 4 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;8&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;9&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;1.125&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;net_bench / 8 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;9&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;8&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;-1&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;0.889&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;net_bench / 16 domains&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;10&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;9&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;-1&lt;/td&gt;&#xA;&lt;td style=&#34;text-align:right&#34;&gt;0.900&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h3 id=&#34;scoped-bench&#34;&gt;scoped_bench&lt;/h3&gt;&#xA;&lt;p&gt;The &amp;ldquo;scoped_bench&amp;rdquo; ran as well, but the results were all zero.&#xA;(I have not investigated this further; &lt;code&gt;net_bench&lt;/code&gt; and &lt;code&gt;scoped_bench&lt;/code&gt; were not expected to take a big performance hit from the refactoring.)&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/landlock-path-walk-inversion-experiment/</guid>
      <pubDate>Fri, 08 May 2026 21:51:04 +0000</pubDate>
    </item>
    <item>
      <title>Shell Tricks: Extract text between two regexes</title>
      <link>https://blog.gnoack.org/post/extracting-between-two-regexes</link>
      <description>&lt;p&gt;To extract text between two regular expressions, use &lt;code&gt;sed -n &#39;/first_regex/second_regex/p&#39;&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;For instance, to &lt;strong&gt;ad-hoc extract the function definition&lt;/strong&gt; of the test &lt;code&gt;tsync_override_log_subdomains_off&lt;/code&gt;, use:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;sed -n &#39;/TEST_F(audit, tsync_override_log_subdomains_off)/,/^}/p&#39; \&#xA;    tools/testing/selftests/landlock/audit_test.c&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This assumes that the function ends with a single &lt;code&gt;}&lt;/code&gt; on an individual line.&#xA;But that tends to be a given.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;awk&lt;/code&gt; offers similar features.&#xA;This invocation finds the definitions of static functions with &lt;code&gt;report_fixup&lt;/code&gt; in their names, and prefixes these findings with the filenames:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;awk &#39;/^static.*report_fixup/,/^}/ { print FILENAME, $0; }&#39; *.c&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;But well, &lt;code&gt;awk&lt;/code&gt; is awkward.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/extracting-between-two-regexes/</guid>
      <pubDate>Fri, 08 May 2026 19:15:34 +0000</pubDate>
    </item>
    <item>
      <title>Landlock Multithreaded Policy Enforcement</title>
      <link>https://blog.gnoack.org/post/landlock-tsync</link>
      <description>&lt;p&gt;With Linux 7.0, Landlock gains the new &lt;a href=&#34;https://wiki.gnoack.org/LandlockMultithreadedEnforcement&#34;&gt;&lt;code&gt;LANDLOCK_RESTRICT_SELF_TSYNC&lt;/code&gt; feature&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;With this new flag to the &lt;code&gt;landlock_restrict_self(2)&lt;/code&gt; system call,&#xA;the Landlock policy enforcement is &lt;strong&gt;applied to the entire process&lt;/strong&gt;&#xA;rather than just the calling thread.  (The naming is analogous to&#xA;the similarly named &lt;code&gt;SECCOMP_FILTER_FLAG_TSYNC&lt;/code&gt; flag for Seccomp-BPF.)&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-old-workaround&#34;&gt;The old workaround&lt;/h2&gt;&#xA;&lt;p&gt;This works around Landlock&amp;rsquo;s need for &lt;a href=&#34;https://sites.google.com/site/fullycapable/who-ordered-libpsx&#34;&gt;libpsx&lt;/a&gt; in multithreaded environments.  Libpsx is a user-space library which uses low-level trickery to make every thread call the Landlock system call directly.  This worked to some extent, but had the drawback that with that, all the different threads were enforcing the same policy, but it still resulted in technically distinct Landlock sandboxes - and that makes a difference in some corner cases.&lt;/p&gt;&#xA;&lt;h2 id=&#34;programming-language-support&#34;&gt;Programming language support&lt;/h2&gt;&#xA;&lt;h3 id=&#34;c&#34;&gt;C&lt;/h3&gt;&#xA;&lt;p&gt;Simple C programs which enforce Landlock at the start of &lt;code&gt;main()&lt;/code&gt; won&amp;rsquo;t need this feature, but it can be useful when you are using Landlock in scenarios that are already multithreaded, like multithreaded frameworks or implicitly multithreaded programming languages.&lt;/p&gt;&#xA;&lt;h3 id=&#34;go&#34;&gt;Go&lt;/h3&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/landlock-lsm/go-landlock/releases/tag/v0.8.0&#34;&gt;Go-Landlock library moves to version 0.8.0&lt;/a&gt; and offers support for this new feature.  To update, update your dependency on Go-Landlock to version 0.8.0.&lt;/p&gt;&#xA;&lt;p&gt;No change in API usage is needed.&#xA;As Go is inherently multithreaded, Landlock policy enforcements through Go-Landlock will automatically use it if the kernel supports it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;&#xA;&lt;p&gt;More details are in:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/landlock-lsm/go-landlock/issues/53&#34;&gt;https://github.com/landlock-lsm/go-landlock/issues/53&lt;/a&gt; - Go-Landlock bug&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.gnoack.org/LandlockMultithreadedEnforcement&#34;&gt;https://wiki.gnoack.org/LandlockMultithreadedEnforcement&lt;/a&gt; - kernel implementation notes&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;For the curious, I&amp;rsquo;ll give a talk about this feature at the &lt;a href=&#34;https://www.meetup.com/zurich-gophers/events/313802948&#34;&gt;upcoming Go meetup in Zurich on 2026-04-23&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/landlock-tsync/</guid>
      <pubDate>Mon, 13 Apr 2026 18:44:37 +0000</pubDate>
    </item>
    <item>
      <title>Nutella is in space</title>
      <link>https://blog.gnoack.org/post/nutella-in-space</link>
      <description>&lt;p&gt;The big question for science is: Does it stay on the ground in the jar, or does it form floating lumps?&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/nutella-in-space/</guid>
      <pubDate>Thu, 09 Apr 2026 20:35:42 +0000</pubDate>
    </item>
    <item>
      <title>Parameterized testing: You want Inputs and Outputs</title>
      <link>https://blog.gnoack.org/post/parameterized-testing</link>
      <description>&lt;p&gt;Is your test in the business of calculating the expected output from the inputs?&lt;/p&gt;&#xA;&lt;p&gt;Then your test is duplicating the logic from the code under test.&lt;/p&gt;&#xA;&lt;p&gt;There is a reason why &lt;a href=&#34;https://testing.googleblog.com/2014/07/testing-on-toilet-dont-put-logic-in.html&#34;&gt;too much logic in tests is frowned upon&lt;/a&gt;:&#xA;If you implement the same logic twice,&#xA;you are &lt;em&gt;prone to repeat the same mistakes in the test which you already made in the real implementation&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bad&#34;&gt;for c in [&#xA;  Case(socket_type=SOCK_STREAM, fruit=&amp;quot;Apple&amp;quot;),&#xA;  Case(socket_type=SOCK_STREAM, fruit=&amp;quot;Orange&amp;quot;),&#xA;  Case(socket_type=SOCK_DGRAM,  fruit=&amp;quot;Apple&amp;quot;),&#xA;  Case(socket_type=SOCK_DGRAM,  fruit=&amp;quot;Orange&amp;quot;),&#xA;]:&#xA;  # Oh no, surprise logic in the test!&#xA;  # This becomes hard to find when the test gets longer.&#xA;  expected = 1&#xA;  if c.socket_type == SOCK_STREAM and c.fruit == &amp;quot;Orange&amp;quot;:&#xA;    expected = 0  # Expecting nothing in this case&#xA;&#xA;  self.assertEqual(expected, operation(c.socket_type, c.fruit))&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Better is to flatten out the expected results into the test table and remove that logic from the test:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-good&#34;&gt;for c in [&#xA;  Case(socket_type=SOCK_STREAM, fruit=&amp;quot;Apple&amp;quot;,  expected=1),&#xA;  Case(socket_type=SOCK_STREAM, fruit=&amp;quot;Orange&amp;quot;, expected=0),  # special case&#xA;  Case(socket_type=SOCK_DGRAM,  fruit=&amp;quot;Apple&amp;quot;,  expected=1),&#xA;  Case(socket_type=SOCK_DGRAM,  fruit=&amp;quot;Orange&amp;quot;, expected=1),&#xA;]:&#xA;  self.assertEqual(c.expected, operation(c.socket_type, c.fruit))&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/landlock-lsm/go-landlock/blob/main/landlock/restrict_downgrade_test.go&#34;&gt;A non-trivial real-world example&lt;/a&gt; (from the Go-Landlock library).&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-0&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:472px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 472.32 163.253&#34;&gt;&#xA;&lt;path d=&#34;M2.16,161.093L110.16,161.093L110.16,89.0929L2.16,89.0929Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,128,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;115.013&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,128,0)&#34; dominant-baseline=&#34;central&#34;&gt;provided&lt;/text&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;135.173&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,128,0)&#34; dominant-baseline=&#34;central&#34;&gt;inputs&lt;/text&gt;&#xA;&lt;polygon points=&#34;182.16,125.093 170.64,129.413 170.64,120.773&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M110.16,125.093L176.4,125.093&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M182.16,161.093L290.16,161.093L290.16,89.0929L182.16,89.0929Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;236.16&#34; y=&#34;104.933&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;system&lt;/text&gt;&#xA;&lt;text x=&#34;236.16&#34; y=&#34;125.093&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;under&lt;/text&gt;&#xA;&lt;text x=&#34;236.16&#34; y=&#34;145.253&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;test&lt;/text&gt;&#xA;&lt;polygon points=&#34;362.16,125.093 350.64,129.413 350.64,120.773&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M290.16,125.093L356.4,125.093&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M362.16,161.093L470.16,161.093L470.16,89.0929L362.16,89.0929Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,128,0);&#34; /&gt;&#xA;&lt;text x=&#34;416.16&#34; y=&#34;115.013&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,128,0)&#34; dominant-baseline=&#34;central&#34;&gt;expected&lt;/text&gt;&#xA;&lt;text x=&#34;416.16&#34; y=&#34;135.173&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,128,0)&#34; dominant-baseline=&#34;central&#34;&gt;outputs&lt;/text&gt;&#xA;&lt;text x=&#34;236.16&#34; y=&#34;12.24&#34; text-anchor=&#34;middle&#34; font-style=&#34;italic&#34; fill=&#34;rgb(0,128,0)&#34; dominant-baseline=&#34;central&#34;&gt;you want both&lt;/text&gt;&#xA;&lt;text x=&#34;236.16&#34; y=&#34;32.4&#34; text-anchor=&#34;middle&#34; font-style=&#34;italic&#34; fill=&#34;rgb(0,128,0)&#34; dominant-baseline=&#34;central&#34;&gt;inputs and outputs&lt;/text&gt;&#xA;&lt;text x=&#34;236.16&#34; y=&#34;52.56&#34; text-anchor=&#34;middle&#34; font-style=&#34;italic&#34; fill=&#34;rgb(0,128,0)&#34; dominant-baseline=&#34;central&#34;&gt;in the test table&lt;/text&gt;&#xA;&lt;polygon points=&#34;56.16,89.0929 51.84,77.5729 60.48,77.5729&#34; style=&#34;fill:rgb(0,128,0)&#34;/&gt;&#xA;&lt;path d=&#34;M151.546,32.4 L 103.853,32.4 Q 56.16,32.4 56.16,57.8665 L 56.16,83.3329&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,128,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;polygon points=&#34;416.16,89.0929 411.84,77.5729 420.48,77.5729&#34; style=&#34;fill:rgb(0,128,0)&#34;/&gt;&#xA;&lt;path d=&#34;M320.774,32.4 L 368.467,32.4 Q 416.16,32.4 416.16,57.8665 L 416.16,83.3329&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,128,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;This is one of these articles that I&amp;rsquo;m writing just so that I can point to it later. &lt;em&gt;In a code review.&lt;/em&gt; :)&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/parameterized-testing/</guid>
      <pubDate>Wed, 11 Feb 2026 23:58:31 +0000</pubDate>
    </item>
    <item>
      <title>She sells Z-Shells at the Z-Shore</title>
      <link>https://blog.gnoack.org/post/landlock-island</link>
      <description>&lt;p&gt;Mickaël&amp;rsquo;s FOSDEM talk about the &lt;a href=&#34;https://github.com/landlock-lsm/island&#34;&gt;Island sandboxing tool&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://fosdem.org/2026/schedule/event/EW8M3R-island/&#34;&gt;https://fosdem.org/2026/schedule/event/EW8M3R-island/&lt;/a&gt; (with 📽️ video!)&lt;/p&gt;&#xA;&lt;p&gt;The Island sandboxing tool comes with tight integration with the &lt;code&gt;zsh&lt;/code&gt;, to make sandboxing a part of the everyday workflow.&lt;/p&gt;&#xA;&lt;p&gt;When you use that integration, the directory that you are in determines the Landlock policy which is then applied to the commands that you run.&lt;/p&gt;&#xA;&lt;p&gt;🐚&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/landlock-island/</guid>
      <pubDate>Wed, 11 Feb 2026 23:49:43 +0000</pubDate>
    </item>
    <item>
      <title>Martin Luther King Day</title>
      <link>https://blog.gnoack.org/post/mlk-day-2026</link>
      <description>&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/RcVZfJO01NI?si=FOWXlvFOLbnNW1Rr&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;&#xA;&lt;p&gt;The time is overdue. Stevie Wonder is amazing. 🎉&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/mlk-day-2026/</guid>
      <pubDate>Mon, 19 Jan 2026 20:39:26 +0000</pubDate>
    </item>
    <item>
      <title>FOSDEM 2026: It is happening!</title>
      <link>https://blog.gnoack.org/post/fosdem2026</link>
      <description>&lt;p&gt;I will be at FOSDEM 2026 this year,&#xA;with a hunger for Belgian waffles&#xA;and a big bag of freshly printed Landlock stickers.&lt;/p&gt;&#xA;&lt;p&gt;I am looking forward to meeting you all again there!&lt;/p&gt;&#xA;&lt;p&gt;If you are dropping by, and want to chat in person, send me a note!&lt;/p&gt;&#xA;&lt;p&gt;I am collecting links over at &lt;a href=&#34;https://wiki.gnoack.org/FosdemTwentySix&#34;&gt;https://wiki.gnoack.org/FosdemTwentySix&lt;/a&gt;. (You might want to double check regarding a potential railway strike on the 30th.)&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/fosdem2026/</guid>
      <pubDate>Sun, 18 Jan 2026 23:20:25 +0000</pubDate>
    </item>
    <item>
      <title>Google Big Sleep: Linux Vulnerabilities</title>
      <link>https://blog.gnoack.org/post/bigsleep-linux</link>
      <description>&lt;p&gt;I am excited to share that my team at Google,&#xA;Google Big Sleep,&#xA;has published the reports&#xA;for a few (now fixed) Linux kernel vulnerabilities:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://issuetracker.google.com/issues/458654612&#34;&gt;BIGSLEEP-458654612&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://issuetracker.google.com/issues/462435176&#34;&gt;BIGSLEEP-462435176&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://issuetracker.google.com/issues/463332873&#34;&gt;BIGSLEEP-463332873&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The full public list of issues discovered by Google Big Sleep&#xA;is at &lt;a href=&#34;https://goo.gle/bigsleep&#34;&gt;https://goo.gle/bigsleep&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/bigsleep-linux/</guid>
      <pubDate>Tue, 06 Jan 2026 19:45:46 +0000</pubDate>
    </item>
    <item>
      <title>Lore links in mutt</title>
      <link>https://blog.gnoack.org/post/lore-links-in-mutt</link>
      <description>&lt;p&gt;The following script takes an email on stdin and constructs the associated &lt;a href=&#34;https://lore.kernel.org&#34;&gt;lore.kernel.org&lt;/a&gt; link based on the email&amp;rsquo;s &lt;code&gt;Message-ID&lt;/code&gt; header.  The script copies the link to the Wayland clipboard and posts a notification.&lt;/p&gt;&#xA;&lt;p&gt;The escaping story is not perfect, but at least it is secure.  (It only supports the most common characters in the message ID, but then it also can&amp;rsquo;t accidentally be misused for escaping shenanigans.)&lt;/p&gt;&#xA;&lt;p&gt;The script &lt;code&gt;email-to-lore&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;#!/bin/sh&#xA;awk &#39;&#xA;/^Message-ID: &amp;lt;[a-zA-Z0-9/+=.@_-]+&amp;gt;/ {&#xA;  match($2, /&amp;lt;([^&amp;gt;]+)/, a);&#xA;  system(&amp;quot;wl-copy -- https://lore.kernel.org/all/&amp;quot; a[1] &amp;quot;/&amp;quot;);&#xA;  system(&amp;quot;notify-send --expire-time=5000 \&amp;quot;Copied to clipboard\&amp;quot; https://lore.kernel.org/&amp;quot; a[1] &amp;quot;/&amp;quot;);&#xA;}&#xA;&#39;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The script is registered in the &lt;a href=&#34;http://mutt.org&#34;&gt;&lt;code&gt;mutt&lt;/code&gt;&lt;/a&gt; configuration in the following way:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;macro pager l &amp;quot;| /path/to/email-to-lore&amp;lt;enter&amp;gt;&amp;quot; &amp;quot;Copy a Lore link to the clipboard&amp;quot;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;When you now hit &lt;code&gt;l&lt;/code&gt; in the mutt email view (pager), you get a desktop notification indicating that the link has been copied to the clipboard and you can use the usual shortcuts to paste it.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;FYI: Existing alternatives:&lt;/strong&gt;&#xA;An alternative to this is the Python &lt;a href=&#34;https://github.com/danrue/lorifier&#34;&gt;&lt;code&gt;lorifier&lt;/code&gt;&lt;/a&gt; script,&#xA;which adds the Lore link to the mail headers before display.  &lt;code&gt;lorifier&lt;/code&gt; is probably smarter about detecting the mailing list which the mail was posted to, whereas my awk script only generates the &amp;ldquo;all&amp;rdquo; link.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/lore-links-in-mutt/</guid>
      <pubDate>Wed, 24 Dec 2025 11:21:34 +0000</pubDate>
    </item>
    <item>
      <title>Listening on an ephemeral TCP port</title>
      <link>https://blog.gnoack.org/post/ephemeral-listen</link>
      <description>&lt;h2 id=&#34;the-classic-tcp-clientserver-interaction&#34;&gt;The classic TCP client/server interaction&lt;/h2&gt;&#xA;&lt;p&gt;When a TCP client calls &lt;a href=&#34;https://man.gnoack.org/2/connect&#34;&gt;&lt;em&gt;connect&lt;/em&gt;(2)&lt;/a&gt;, it usually gets assigned a port number from the &lt;em&gt;ephemeral port range&lt;/em&gt; which is not used yet.&lt;/p&gt;&#xA;&lt;p&gt;The server, on the other hand, usually calls &lt;a href=&#34;https://man.gnoack.org/2/bind&#34;&gt;&lt;em&gt;bind&lt;/em&gt;(2)&lt;/a&gt;&#xA;to set the port that it will listen on.&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-0&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:609px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 609.754 432.72&#34;&gt;&#xA;&lt;text x=&#34;459&#34; y=&#34;12.24&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Server&lt;/text&gt;&#xA;&lt;polygon points=&#34;459,56.16 454.68,44.64 463.32,44.64&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M459,27.36L459,50.4&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M410.4,84.96L507.6,84.96L507.6,56.16L410.4,56.16Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;459&#34; y=&#34;70.56&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;socket()&lt;/text&gt;&#xA;&lt;polygon points=&#34;459,113.76 454.68,102.24 463.32,102.24&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M459,84.96L459,108&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M410.4,142.56L507.6,142.56L507.6,113.76L410.4,113.76Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;459&#34; y=&#34;128.16&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;bind()&lt;/text&gt;&#xA;&lt;polygon points=&#34;459,171.36 454.68,159.84 463.32,159.84&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M459,142.56L459,165.6&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M410.4,200.16L507.6,200.16L507.6,171.36L410.4,171.36Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;459&#34; y=&#34;185.76&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;listen()&lt;/text&gt;&#xA;&lt;polygon points=&#34;459,228.96 454.68,217.44 463.32,217.44&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M459,200.16L459,223.2&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M410.4,257.76L507.6,257.76L507.6,228.96L410.4,228.96Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;459&#34; y=&#34;243.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;accept()&lt;/text&gt;&#xA;&lt;polygon points=&#34;459,286.56 454.68,275.04 463.32,275.04&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M459,257.76L459,280.8&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M410.4,315.36L507.6,315.36L507.6,286.56L410.4,286.56Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;459&#34; y=&#34;300.96&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;recv()&lt;/text&gt;&#xA;&lt;polygon points=&#34;459,344.16 454.68,332.64 463.32,332.64&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M459,315.36L459,338.4&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M410.4,372.96L507.6,372.96L507.6,344.16L410.4,344.16Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;459&#34; y=&#34;358.56&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;send()&lt;/text&gt;&#xA;&lt;polygon points=&#34;459,401.76 454.68,390.24 463.32,390.24&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M459,372.96L459,396&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M410.4,430.56L507.6,430.56L507.6,401.76L410.4,401.76Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;459&#34; y=&#34;416.16&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;close()&lt;/text&gt;&#xA;&lt;text x=&#34;245.16&#34; y=&#34;12.24&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Client&lt;/text&gt;&#xA;&lt;polygon points=&#34;245.16,56.16 240.84,44.64 249.48,44.64&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M245.16,27.36L245.16,50.4&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M196.56,84.96L293.76,84.96L293.76,56.16L196.56,56.16Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;245.16&#34; y=&#34;70.56&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;socket()&lt;/text&gt;&#xA;&lt;polygon points=&#34;245.16,228.96 240.84,217.44 249.48,217.44&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M245.16,84.96L245.16,223.2&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M196.56,257.76L293.76,257.76L293.76,228.96L196.56,228.96Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;245.16&#34; y=&#34;243.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;connect()&lt;/text&gt;&#xA;&lt;polygon points=&#34;245.16,286.56 240.84,275.04 249.48,275.04&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M245.16,257.76L245.16,280.8&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M196.56,315.36L293.76,315.36L293.76,286.56L196.56,286.56Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;245.16&#34; y=&#34;300.96&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;send()&lt;/text&gt;&#xA;&lt;polygon points=&#34;245.16,344.16 240.84,332.64 249.48,332.64&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M245.16,315.36L245.16,338.4&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M196.56,372.96L293.76,372.96L293.76,344.16L196.56,344.16Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;245.16&#34; y=&#34;358.56&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;recv()&lt;/text&gt;&#xA;&lt;polygon points=&#34;245.16,401.76 240.84,390.24 249.48,390.24&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M245.16,372.96L245.16,396&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M196.56,430.56L293.76,430.56L293.76,401.76L196.56,401.76Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;245.16&#34; y=&#34;416.16&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;close()&lt;/text&gt;&#xA;&lt;polygon points=&#34;293.76,243.36 305.28,239.04 305.28,247.68&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;polygon points=&#34;410.4,243.36 398.88,247.68 398.88,239.04&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M299.52,243.36L404.64,243.36&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:7.2,7.2;&#34; /&gt;&#xA;&lt;text x=&#34;352.08&#34; y=&#34;231.66&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Rendezvous&lt;/text&gt;&#xA;&lt;polygon points=&#34;410.4,300.96 398.88,305.28 398.88,296.64&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M293.76,300.96L404.64,300.96&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:7.2,7.2;&#34; /&gt;&#xA;&lt;polygon points=&#34;293.76,358.56 305.28,354.24 305.28,362.88&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M410.4,358.56L299.52,358.56&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:7.2,7.2;&#34; /&gt;&#xA;&lt;path d=&#34;M182.16,387.36L522,387.36L522,272.16L182.16,272.16Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;polygon points=&#34;196.56,243.36 185.04,247.68 185.04,239.04&#34; style=&#34;fill:rgb(255,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M190.8,243.36L110.16,243.36&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(255,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;text x=&#34;153.36&#34; y=&#34;231.66&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(255,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;get port&lt;/text&gt;&#xA;&lt;ellipse cx=&#34;56.16&#34; cy=&#34;243.36&#34; rx=&#34;54&#34; ry=&#34;36&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(255,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;233.28&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(255,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;ephemeral&lt;/text&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;253.44&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(255,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;port range&lt;/text&gt;&#xA;&lt;text x=&#34;507.6&#34; y=&#34;118.08&#34; text-anchor=&#34;start&#34; font-style=&#34;italic&#34; fill=&#34;rgb(255,0,0)&#34; dominant-baseline=&#34;central&#34;&gt; determines&lt;/text&gt;&#xA;&lt;text x=&#34;507.6&#34; y=&#34;138.24&#34; text-anchor=&#34;start&#34; font-style=&#34;italic&#34; fill=&#34;rgb(255,0,0)&#34; dominant-baseline=&#34;central&#34;&gt; port here&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;h2 id=&#34;some-less-known-fun-facts&#34;&gt;Some less known fun facts&lt;/h2&gt;&#xA;&lt;h3 id=&#34;servers-that-listen-on-random-ports&#34;&gt;Servers that listen on random ports&lt;/h3&gt;&#xA;&lt;p&gt;While a server will usually &lt;a href=&#34;https://man.gnoack.org/2/bind&#34;&gt;&lt;em&gt;bind&lt;/em&gt;(2)&lt;/a&gt; itself to a fixed port number, it can absolutely omit that, and then, when it calls &lt;a href=&#34;https://man.gnoack.org/2/listen&#34;&gt;&lt;em&gt;listen&lt;/em&gt;(2)&lt;/a&gt;, &lt;strong&gt;the server will get an ephemeral port assigned by the kernel&lt;/strong&gt; which it will then listen on.&#xA;(This is similar to setting the bound port number to 0.)&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-1&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:268px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 268.69 256.32&#34;&gt;&#xA;&lt;text x=&#34;50.76&#34; y=&#34;12.24&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Server&lt;/text&gt;&#xA;&lt;polygon points=&#34;50.76,56.16 46.44,44.64 55.08,44.64&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M50.76,27.36L50.76,50.4&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,84.96L99.36,84.96L99.36,56.16L2.16,56.16Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;50.76&#34; y=&#34;70.56&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;socket()&lt;/text&gt;&#xA;&lt;polygon points=&#34;50.76,113.76 46.44,102.24 55.08,102.24&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M50.76,84.96L50.76,108&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,142.56L99.36,142.56L99.36,113.76L2.16,113.76Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;50.76&#34; y=&#34;128.16&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;listen()&lt;/text&gt;&#xA;&lt;polygon points=&#34;50.76,171.36 46.44,159.84 55.08,159.84&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M50.76,142.56L50.76,165.6&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,200.16L99.36,200.16L99.36,171.36L2.16,171.36Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;50.76&#34; y=&#34;185.76&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;accept()&lt;/text&gt;&#xA;&lt;polygon points=&#34;50.76,228.96 46.44,217.44 55.08,217.44&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M50.76,200.16L50.76,223.2&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;50.76&#34; y=&#34;244.08&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;...&lt;/text&gt;&#xA;&lt;polygon points=&#34;50.76,99.36 62.28,95.04 62.28,103.68&#34; style=&#34;fill:rgb(255,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M56.52,99.36L122.76,99.36&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(255,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;197.525&#34; y=&#34;99.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(255,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;bind() not called&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;h3 id=&#34;clients-that-use-a-specific-port-number&#34;&gt;Clients that use a specific port number&lt;/h3&gt;&#xA;&lt;p&gt;If a client socket uses &lt;a href=&#34;https://man.gnoack.org/2/bind&#34;&gt;&lt;em&gt;bind&lt;/em&gt;(2)&lt;/a&gt; before &lt;a href=&#34;https://man.gnoack.org/2/connect&#34;&gt;&lt;em&gt;connect&lt;/em&gt;(2)&lt;/a&gt;, &lt;strong&gt;the client can enforce using a specific port number for its own side&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-2&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:234px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 234.23 256.32&#34;&gt;&#xA;&lt;text x=&#34;50.76&#34; y=&#34;12.24&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Client&lt;/text&gt;&#xA;&lt;polygon points=&#34;50.76,56.16 46.44,44.64 55.08,44.64&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M50.76,27.36L50.76,50.4&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,84.96L99.36,84.96L99.36,56.16L2.16,56.16Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;50.76&#34; y=&#34;70.56&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;socket()&lt;/text&gt;&#xA;&lt;polygon points=&#34;50.76,113.76 46.44,102.24 55.08,102.24&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M50.76,84.96L50.76,108&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,142.56L99.36,142.56L99.36,113.76L2.16,113.76Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(255,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;50.76&#34; y=&#34;128.16&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(255,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;bind()&lt;/text&gt;&#xA;&lt;polygon points=&#34;50.76,171.36 46.44,159.84 55.08,159.84&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M50.76,142.56L50.76,165.6&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,200.16L99.36,200.16L99.36,171.36L2.16,171.36Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;50.76&#34; y=&#34;185.76&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;connect()&lt;/text&gt;&#xA;&lt;polygon points=&#34;50.76,228.96 46.44,217.44 55.08,217.44&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M50.76,200.16L50.76,223.2&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;50.76&#34; y=&#34;244.08&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;...&lt;/text&gt;&#xA;&lt;text x=&#34;99.36&#34; y=&#34;118.08&#34; text-anchor=&#34;start&#34; font-style=&#34;italic&#34; fill=&#34;rgb(255,0,0)&#34; dominant-baseline=&#34;central&#34;&gt; binds the&lt;/text&gt;&#xA;&lt;text x=&#34;99.36&#34; y=&#34;138.24&#34; text-anchor=&#34;start&#34; font-style=&#34;italic&#34; fill=&#34;rgb(255,0,0)&#34; dominant-baseline=&#34;central&#34;&gt; client-side port&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;h2 id=&#34;reference&#34;&gt;Reference&lt;/h2&gt;&#xA;&lt;p&gt;The best description I found of this is in &lt;a href=&#34;https://man.gnoack.org/7/ip&#34;&gt;&lt;em&gt;ip&lt;/em&gt;(7)&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote class=&#34;def&#34;&gt;&lt;p&gt;An ephemeral port is allocated to a socket in the following circumstances:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;the port number in a socket address is specified as 0 when calling bind(2);&lt;/li&gt;&#xA;&lt;li&gt;listen(2) is called on a stream socket that was not previously bound;&lt;/li&gt;&#xA;&lt;li&gt;connect(2) was called on a socket that was not previously bound;&lt;/li&gt;&#xA;&lt;li&gt;sendto(2) is called on a datagram socket that was not previously bound.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;So the behaviour of &lt;a href=&#34;https://man.gnoack.org/2/bind&#34;&gt;&lt;em&gt;bind&lt;/em&gt;(2)&lt;/a&gt; actually works the same on both client and server sockets.  It&amp;rsquo;s just in daily use where it only gets used for server sockets.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://lore.kernel.org/linux-security-module/ZoKB7bl41ZOiiXmF@google.com/&#34;&gt;Previous discussion on the kernel mailing lists&lt;/a&gt;&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/ephemeral-listen/</guid>
      <pubDate>Fri, 14 Nov 2025 19:26:44 +0000</pubDate>
    </item>
    <item>
      <title>Integer overflow checking with C23</title>
      <link>https://blog.gnoack.org/post/int_overflow_c23</link>
      <description>&lt;p&gt;While I wasn&amp;rsquo;t looking, &lt;a href=&#34;https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf#page=330&#34;&gt;C23 has standardized checked integer arithmetic functions&lt;/a&gt;, replacing the old &amp;ldquo;&lt;code&gt;__builtin_mul_overflow()&lt;/code&gt;&amp;rdquo; compiler intrinsics that shipped with &lt;a href=&#34;https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html&#34;&gt;GCC&lt;/a&gt; and &lt;a href=&#34;https://clang.llvm.org/docs/LanguageExtensions.html#checked-arithmetic-builtins&#34;&gt;Clang&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdckdint.h&amp;gt;&#xA;bool ckd_add(type1 *result, type2 a, type3 b);&#xA;bool ckd_sub(type1 *result, type2 a, type3 b);&#xA;bool ckd_mul(type1 *result, type2 a, type3 b);&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The operations are performed equivalently to doing them in the mathematically reasonable way (as normal &lt;a href=&#34;https://en.wikipedia.org/wiki/Integer&#34;&gt;integers in ℤ&lt;/a&gt;, not modulo-something), and then truncating them to fit into the &lt;code&gt;*result&lt;/code&gt; integer.&#xA;The function returns &lt;code&gt;true&lt;/code&gt; if the resulting value could not be represented in &lt;code&gt;*result&lt;/code&gt;&amp;rsquo;s integer type.&lt;/p&gt;&#xA;&lt;p&gt;To check for overflow of two integers &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;, define a variable for the calculation result and do:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-good&#34;&gt;int result;&#xA;if (ckd_mul(&amp;amp;result, a, b)) {&#xA;  errx(1, &amp;quot;a * b overflow or wraparound&amp;quot;);&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;These semantics remove having to worry of the unintuitive difference between (undefined) integer overflow and (well-defined) unsigned integer wraparound.&#xA;(The &amp;ldquo;arithmetic&amp;rdquo; ways for checking overflow/wraparound differ substantially for signed and unsigned integers, otherwise.)&lt;/p&gt;&#xA;&lt;p&gt;The feature is available in:&#xA;&lt;a href=&#34;https://gcc.gnu.org/projects/c-status.html#c23&#34;&gt;GCC 14+&lt;/a&gt;,&#xA;&lt;a href=&#34;https://clang.llvm.org/c_status.html#c2x&#34;&gt;Clang 18+&lt;/a&gt;,&#xA;both released in Q2 2024.&lt;/p&gt;&#xA;&lt;h2 id=&#34;further-references&#34;&gt;Further references&lt;/h2&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://wiki.sei.cmu.edu/confluence/pages/viewpage.action?pageId=87152052&#34;&gt;Integer section&lt;/a&gt; of the SEI C Coding Standard wiki has some excellent guidance for correct overflow and wraparound checks, also for older compilers.&#xA;Specifically, these sections are:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.sei.cmu.edu/confluence/display/c/INT30-C.+Ensure+that+unsigned+integer+operations+do+not+wrap&#34;&gt;INT30-C. Ensure that unsigned integer operations do not wrap&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow&#34;&gt;INT32-C. Ensure that operations on signed integers do not result in overflow&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.sei.cmu.edu/authors/david-svoboda/&#34;&gt;David Svoboda&lt;/a&gt;,&#xA;who is involved here,&#xA;is the same person who &lt;a href=&#34;https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2683.pdf&#34;&gt;proposed the extension to the C23&#xA;standard&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://gustedt.wordpress.com/2022/12/18/checked-integer-arithmetic-in-the-prospect-of-c23/&#34;&gt;Jens Gustedt&amp;rsquo;s&#xA;Blog&lt;/a&gt;&#xA;has a longer post on the topic as well with more practical examples&#xA;and a discussion of how to emulate the new interface based on the&#xA;older macros that GCC and LLVM offered before C23.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;__builtin_mul_overflow()&lt;/code&gt; macro and friends were introduced in&#xA;&lt;a href=&#34;https://gcc.gnu.org/gcc-5/changes.html&#34;&gt;GCC 5 (2015)&lt;/a&gt;&#xA;and &lt;a href=&#34;https://releases.llvm.org/3.4/tools/clang/docs/ReleaseNotes.html#c-language-changes-in-clang&#34;&gt;Clang 3.4 (2014)&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/int_overflow_c23/</guid>
      <pubDate>Sun, 02 Nov 2025 20:48:02 +0100</pubDate>
    </item>
    <item>
      <title>Landlock Your Vibe Coding</title>
      <link>https://blog.gnoack.org/post/landlock-your-vibe-coding</link>
      <description>&lt;img style=&#34;float: right; width: 20%; border-radius: 1em; margin: 0.5em;&#34; src=&#34;https://blog.gnoack.org/images/goodvibesonly.png&#34;&gt;&#xA;&lt;p&gt;We&amp;rsquo;ve all heard the horror stories where an unsandboxed coding agent&#xA;deletes the user&amp;rsquo;s entire home directory.&#xA;As a mitigation,&#xA;many vibe coding tools are now using Docker to contain the agent&amp;rsquo;s actions.&lt;/p&gt;&#xA;&lt;p&gt;Unfortunately, &lt;strong&gt;Docker is not a good tool to keep the vibe coding agents at bay&lt;/strong&gt;.&#xA;To quote &lt;a href=&#34;https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user&#34;&gt;Docker&amp;rsquo;s own documentation&lt;/a&gt; (emphasis mine):&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;The &lt;code&gt;docker&lt;/code&gt; group &lt;strong&gt;grants root-level privileges to the user&lt;/strong&gt;.&#xA;For details on how this impacts security in your system,&#xA;see &lt;a href=&#34;https://docs.docker.com/engine/security/#docker-daemon-attack-surface&#34;&gt;Docker Daemon Attack Surface&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;So while it keeps the agent from doing harm outside of the Docker container,&#xA;installing Docker also removes the security boundary between your normal user and root.&#xA;Docker is not something that your normal user account should have access to.&lt;/p&gt;&#xA;&lt;p&gt;So as a stop-gap measure, for systems where you don&amp;rsquo;t want to install Docker,&#xA;here is a little ad-hoc sandboxing script which runs &lt;a href=&#34;https://github.com/google-gemini/gemini-cli&#34;&gt;Google&amp;rsquo;s &lt;code&gt;gemini-cli&lt;/code&gt;&lt;/a&gt; in a &lt;a href=&#34;https://landlock.io/&#34;&gt;Landlock&lt;/a&gt; sandbox:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;#!/bin/sh&#xA;&#xA;RDIR=execute,read-file,read-dir&#xA;&#xA;# A reasonable subset of write accesses&#xA;RWDIR=$RDIR,write-file,remove-dir,remove-file,make-dir,make-reg,make-sock,make-fifo,make-sym,refer,truncate&#xA;&#xA;if [ &amp;quot;$PWD&amp;quot; = &amp;quot;$HOME&amp;quot; ]; then&#xA;    echo &amp;quot;Run this from your project directory only&amp;quot;&#xA;    exit 1&#xA;fi&#xA;&#xA;mkdir -p $HOME/.gemini  # ensure this exists, no-op if it preexists&#xA;&#xA;setpriv \&#xA;  --landlock-access fs \&#xA;  --landlock-rule path-beneath:$RWDIR:$HOME/.gemini \&#xA;  --landlock-rule path-beneath:$RDIR:/etc \&#xA;  --landlock-rule path-beneath:$RDIR:/bin \&#xA;  --landlock-rule path-beneath:$RDIR:/usr \&#xA;  --landlock-rule path-beneath:$RDIR:/lib \&#xA;  --landlock-rule path-beneath:$RWDIR:$PWD \&#xA;  /usr/bin/gemini&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The tool used here it &lt;a href=&#34;https://man.gnoack.org/1/setpriv&#34;&gt;&lt;em&gt;setpriv&lt;/em&gt;(1)&lt;/a&gt; from the &lt;code&gt;util-linux&lt;/code&gt; package.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Obligatory warning&lt;/strong&gt;:&#xA;This Landlock invocation restricts access to the &lt;strong&gt;file system&lt;/strong&gt;: It can keep an agent from reading your SSH keys and from seeing of modifying the content of your files outside &lt;code&gt;~/.gemini&lt;/code&gt; and the current directory.  &lt;strong&gt;It does not&lt;/strong&gt; restrict access to networking, Unix signals and a variety of other things.  It keeps your files safe, but you still should not store your prod database credentials within the agent&amp;rsquo;s reach.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/landlock-your-vibe-coding/</guid>
      <pubDate>Sat, 02 Aug 2025 20:08:48 +0200</pubDate>
    </item>
    <item>
      <title>Argument passing adventures</title>
      <link>https://blog.gnoack.org/post/argument-passing-adventures</link>
      <description>&lt;img src=&#34;/images/gnu-mouse-300.png&#34; style=&#34;float: right; margin-left: 0.5em; max-width: 40%;&#34;&gt;&#xA;&lt;p&gt;When I &lt;a href=&#34;https://blog.gnoack.org/post/emacs-mouse-bug/&#34;&gt;posted about the Emacs mouse&#xA;bug&lt;/a&gt; in February, I&#xA;looked into the patch set again, and of course spotted more mistakes.&#xA;&lt;a href=&#34;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit?id=ee6a44da3c87cf64d67dd02be8c0127a5bf56175&#34;&gt;This&#xA;patch&lt;/a&gt;&#xA;simplifies and corrects some inconsistent logic in where&#xA;&lt;code&gt;CAP_SYS_ADMIN&lt;/code&gt; was required.  The change does not make a difference&#xA;for existing terminal mouse driver programs like GPM or Consolation,&#xA;but it is noteworthy for the coding aspect:&lt;/p&gt;&#xA;&lt;h2 id=&#34;fantastic-new-ways-of-passing-arguments&#34;&gt;Fantastic new ways of passing arguments&lt;/h2&gt;&#xA;&lt;p&gt;In the kernel code, the decision whether to apply &lt;code&gt;CAP_SYS_ADMIN&lt;/code&gt; is&#xA;made by looking at an &amp;ldquo;enum&amp;rdquo; value (&lt;code&gt;sel_mode&lt;/code&gt;) which defines the&#xA;operation to be run, in a struct which also carries arguments:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;struct {&#xA;    char  subcode;&#xA;    short xs, ys, xe, ye;&#xA;    short sel_mode;&#xA;};&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;And in most cases, &lt;code&gt;sel_mode&lt;/code&gt; &lt;em&gt;was&lt;/em&gt; also used like an enum.&lt;/p&gt;&#xA;&lt;p&gt;However, at some point someone added the operation&#xA;&lt;code&gt;TIOCL_SELMOUSEREPORT&lt;/code&gt; and found that it required an additional&#xA;argument.  But the existing argument struct did not have space for it!&#xA;What to do?&lt;/p&gt;&#xA;&lt;p&gt;Instead of implementing the operation with a different argument&#xA;struct, the implementers then presumably figured that it would be&#xA;easier to implement if they re-used the existing struct.  So they made&#xA;&lt;code&gt;TIOCL_SELMOUSEREPORT&lt;/code&gt; the number 16 (in binary: 10000) and then&#xA;&lt;strong&gt;used the lower 4 bits of the same enum as an additional argument&lt;/strong&gt;.&#xA;(It&amp;rsquo;s the 90&amp;rsquo;s.  Go for it!)&lt;/p&gt;&#xA;&lt;p&gt;None of this got documented either (&lt;a href=&#34;https://josvisser.substack.com/p/chaos-is-the-price-of-freedom&#34;&gt;Chaos is the price of&#xA;freedom&lt;/a&gt;),&#xA;so I also ended up submitting documentation for it to the &lt;a href=&#34;https://man.gnoack.org/2const/TIOCLINUX&#34;&gt;TIOCLINUX&#xA;man page&lt;/a&gt;, in the section&#xA;about &lt;code&gt;TIOCLINUX&lt;/code&gt;&amp;rsquo;s &lt;code&gt;TIOCL_SETSEL&lt;/code&gt; subcode (highlight mine):&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;h4 id=&#34;tiocl-selmousereport&#34;&gt;TIOCL_SELMOUSEREPORT&lt;/h4&gt;&#xA;&lt;p&gt;Make the terminal report (&lt;code&gt;xs&lt;/code&gt;, &lt;code&gt;ys&lt;/code&gt;) as the current mouse location&#xA;using the &lt;a href=&#34;https://man.gnoack.org/1/xterm&#34;&gt;xterm(1)&lt;/a&gt; mouse tracking&#xA;protocol (see&#xA;&lt;a href=&#34;https://man.gnoack.org/4/console_codes&#34;&gt;console_codes(4)&lt;/a&gt;).  &lt;strong&gt;The&#xA;lower 4 bits of &lt;code&gt;sel_mode&lt;/code&gt; (&lt;code&gt;TIOCL_SELBUTTONMASK&lt;/code&gt;) indicate the&#xA;desired button press and modifier key information for the mouse&#xA;event.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;[&amp;hellip;]&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;All of these features are luckily highly obscure and seldomly looked&#xA;at &amp;ndash; I really hope that no one will actually need this documentation&#xA;again, but if they do, it&amp;rsquo;s documented now.&lt;/p&gt;&#xA;&lt;h2 id=&#34;whats-the-takeaway-here&#34;&gt;What&amp;rsquo;s the takeaway here?&lt;/h2&gt;&#xA;&lt;p&gt;Admittedly, I should have read the kernel code better and should not&#xA;have tripped over this.  But also, in my defense, people are trained&#xA;to recognize conventional programming patterns.  If code looks&#xA;conventional on the surface but deviates in surprising ways, it&#xA;becomes easy to miss.  Don&amp;rsquo;t do this.&lt;/p&gt;&#xA;&lt;p&gt;Along the way I also &lt;a href=&#34;https://wiki.gnoack.org/TerminalMouseSupport&#34;&gt;wrote up my understanding of Linux&amp;rsquo;s terminal&#xA;mouse support&lt;/a&gt;.  (I&#xA;should note that I am not a terminal driver expert, and it&amp;rsquo;s possible&#xA;that I am misunderstanding parts of it.)&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit?id=ee6a44da3c87cf64d67dd02be8c0127a5bf56175&#34;&gt;The&#xA;patch&lt;/a&gt;&#xA;is now in all stable kernels (6.12.26+, 6.14.5+).  Versions before 6.7&#xA;are unaffected.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/argument-passing-adventures/</guid>
      <pubDate>Fri, 02 May 2025 13:54:24 +0200</pubDate>
    </item>
    <item>
      <title>I broke Emacs mouse support on the console</title>
      <link>https://blog.gnoack.org/post/emacs-mouse-bug</link>
      <description>&lt;img src=&#34;/images/gnu-mouse-300.png&#34; style=&#34;float: right; margin-left: 0.5em; max-width: 40%;&#34;&gt;&#xA;&lt;p&gt;I accidentally broke Emacs mouse support on the Linux console. o_O&lt;/p&gt;&#xA;&lt;p&gt;The TIOCLINUX patch for disabling dangerous IOCTLs for the Linux&#xA;console driver (&lt;a href=&#34;https://wiki.gnoack.org/TiocstiTioclinuxSecurityProblems&#34;&gt;background discussed on the&#xA;Wiki&lt;/a&gt;) ended&#xA;up accidentally making the mouse cursor invisible on the Linux console&#xA;(the proper text mode one, not xterm).&lt;/p&gt;&#xA;&lt;p&gt;I apologize for breaking this. We luckily found a patch for it and it&#xA;is now rolling out to stable Linux kernels.&lt;/p&gt;&#xA;&lt;p&gt;If your mouse does not work as expected in Emacs on the Linux console,&#xA;please update your kernel to a newer release.&lt;/p&gt;&#xA;&lt;p&gt;It boggles me that this managed to slip through the cracks in the&#xA;initial review, after we had done such an exhaustive search for&#xA;remaining users in Debian code search.  Lesson learned.&lt;/p&gt;&#xA;&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://lore.kernel.org/all/ee3ec63269b43b34e1c90dd8c9743bf8@finder.org/&#34;&gt;GPM &amp;amp; Emacs broken in Linux 6.7&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://lists.gnu.org/archive/html/bug-gnu-emacs/2024-11/msg00275.html&#34;&gt;Emacs bug 74220 - Invisible cursor&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://wiki.gnoack.org/TiocstiTioclinuxSecurityProblems&#34;&gt;Longer Wiki description about the patch that broke it&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/emacs-mouse-bug/</guid>
      <pubDate>Wed, 12 Feb 2025 17:11:09 +0100</pubDate>
    </item>
    <item>
      <title>Git without a hosted platform</title>
      <link>https://blog.gnoack.org/post/git-over-ssh</link>
      <description>&lt;p&gt;People have apparently started believing that in order to use git,&#xA;you&amp;rsquo;d have to also have to host the project on Github or Gitlab:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;A few weeks ago, a friend told me that he keeps &lt;a href=&#34;https://www.passwordstore.org/&#34;&gt;his &lt;code&gt;pass&lt;/code&gt; password repository&lt;/a&gt; on Github.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://beej.us/guide/bggit/&#34;&gt;Beej wrote a guide on git&lt;/a&gt; and the Github workflow is very prominent in it&lt;/li&gt;&#xA;&lt;li&gt;I came home from FOSDEM with a Github and a Gitlab sticker, but not with one for &lt;code&gt;git send-email&lt;/code&gt; or cgit.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I feel that is is underreported that&#xA;&lt;strong&gt;you can easily host Git repos on a normal Unix account&lt;/strong&gt;.&#xA;I use this all the time myself for my personal projects &amp;ndash;&#xA;I maintain far more repositories locally over SSH than I expose on hosted platforms.&#xA;This is not visible on the public internet,&#xA;but maybe that makes it especially mention-worthy.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-its-done&#34;&gt;How it&amp;rsquo;s done&lt;/h2&gt;&#xA;&lt;p&gt;All you need is an account on a Unix/Linux machine, which you can reach over SSH.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;On the remote side, set up the repo:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;mkdir -p ~/repos/foobar.git&#xA;cd ~/repos/foobar.git&#xA;git init --bare&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;On the local side, set up the remote:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;git remote add foobar user@hostname:repos/foobar.git&#xA;git push --set-upstream foobar main&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;There is not much more to it. Now you can push and pull.&lt;/p&gt;&#xA;&lt;p&gt;A more detailed description that also covers SSH keys can be found in the &lt;a href=&#34;https://git-scm.com/book/en/v2/Git-on-the-Server-Setting-Up-the-Server&#34;&gt;Git Book&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-if-i-need-more&#34;&gt;What if I need more?&lt;/h2&gt;&#xA;&lt;img src=&#34;/images/git-send-email-300.png&#34; style=&#34;float: right; border: 1px solid black; margin-left: 0.5em; max-width: 40%;&#34;&gt;&#xA;&lt;p&gt;The approach scales and extends to bigger endeavors as well.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Web UI:&lt;/strong&gt; You can set up &lt;a href=&#34;https://git.zx2c4.com/cgit/about/&#34;&gt;cgit&lt;/a&gt; on a web server to render your repositories.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Code review:&lt;/strong&gt; Some projects do code reviews over &lt;a href=&#34;https://git-send-email.io/&#34;&gt;&lt;code&gt;git send-email&lt;/code&gt;&lt;/a&gt;.&#xA;The Linux kernel is known for this and scales up to thousands of developers.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;If a project grows and you want to work with people who can&amp;rsquo;t wrap their heads around this,&#xA;you can still fall back to a hosted platform after the fact and push your repository there later.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/git-over-ssh/</guid>
      <pubDate>Wed, 05 Feb 2025 21:16:03 +0100</pubDate>
    </item>
    <item>
      <title>Dan Bernstein on sandboxing</title>
      <link>https://blog.gnoack.org/post/djb-sandboxing</link>
      <description>&lt;p&gt;Thoughts on sandboxing, found via &lt;a href=&#34;http://www.aaronsw.com/weblog/001502&#34;&gt;Aaron Swartz&amp;rsquo;s&#xA;weblog&lt;/a&gt;, who in turn found it in&#xA;&lt;a href=&#34;https://cr.yp.to/cv/activities-20050107.pdf&#34;&gt;a writeup about Dan Bernstein&amp;rsquo;s 2005 research&#xA;activities&lt;/a&gt; (at the end):&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;The software installed on my newest home computer contains 78&#xA;million lines of C and C++ source code. Presumably there are&#xA;hundreds of thousands of bugs in this code. Removing all of those&#xA;bugs would be quite expensive. Fortunately, there’s a less expensive&#xA;way to eliminate most security problems.&lt;/p&gt;&#xA;&lt;p&gt;Consider, for example, a compressed music track. The UNIX &lt;code&gt;ogg123&lt;/code&gt;&#xA;program and underlying libraries contain 50000 lines of code whose&#xA;sole purpose is to read a compressed music track in Ogg Vorbis&#xA;format and write an uncompressed music track in wave format.&lt;/p&gt;&#xA;&lt;p&gt;UNIX makes it possible (though unnecessarily difficult) to build a&#xA;&lt;code&gt;safeogg&lt;/code&gt; program that does the same conversion &lt;em&gt;and that has no&#xA;other power over the system&lt;/em&gt;. Bugs in the 50000 lines of code are&#xA;then irrelevant to security: if the input is from an attacker who&#xA;seizes control of &lt;code&gt;safeogg&lt;/code&gt;, the most the attacker can do is write&#xA;arbitrary output, which is what the input source was authorized to&#xA;do anyway.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;I&amp;rsquo;m sharing this here because much of the reasoning behind Landlock&#xA;traces its roots back to the same train of thoughts (and also because&#xA;Dan Bernstein lais down the reasoning very nicely).&lt;/p&gt;&#xA;&lt;h2 id=&#34;choice-of-sandboxing-mechanisms&#34;&gt;Choice of sandboxing mechanisms&lt;/h2&gt;&#xA;&lt;p&gt;This is 2005, so the UNIX mechanisms that he suggests for sandboxing&#xA;are:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://man.gnoack.org/2/chroot&#34;&gt;&lt;em&gt;chroot&lt;/em&gt;(2)&lt;/a&gt; to limit access to the filesystem (&lt;em&gt;requires root, or &lt;code&gt;CAP_SYS_CHROOT&lt;/code&gt; on Linux&lt;/em&gt;)&lt;/li&gt;&#xA;&lt;li&gt;using a temporary unique UID to limit access to other processes (&lt;em&gt;requires root&lt;/em&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://man.gnoack.org/2/setrlimit&#34;&gt;&lt;em&gt;setrlimit&lt;/em&gt;(2)&lt;/a&gt; with a hard &lt;code&gt;RLIMIT_NOFILE&lt;/code&gt; of 0 to limit access to the network&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://man.gnoack.org/2/setrlimit&#34;&gt;&lt;em&gt;setrlimit&lt;/em&gt;(2)&lt;/a&gt; with other resource limits to limit RAM resource use&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;(Unfortunately, some of these require higher privileges than what&#xA;normal programs run as, a property that these mechanisms also share&#xA;with classic Linux Security modules like AppArmor and SELinux.)&lt;/p&gt;&#xA;&lt;p&gt;One thing where Landlock&amp;rsquo;s approach differs slightly is that we&#xA;believe that the mechanisms for sandboxing should be available to&#xA;unprivileged processes so that they become maximally useful &amp;ndash;&#xA;software authors for general purpose software generally can not expect&#xA;that their software runs with high privileges, and neither should they&#xA;&amp;ndash; it is absurd if a process needs higher privileges in order to drop&#xA;privileges. (Seccomp was an attempt at a Sandboxing mechanism that did&#xA;not require higher privileges, but &lt;a href=&#34;https://blog.gnoack.org/post/pledge-on-linux/&#34;&gt;Seccomp had other practical&#xA;issues&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;h2 id=&#34;sandboxing-should-be-a-developer-task&#34;&gt;Sandboxing should be a developer task&lt;/h2&gt;&#xA;&lt;p&gt;But the overlap to Landlock&amp;rsquo;s approach is very large &amp;ndash; especially&#xA;when it comes to the idea that the use of the sandboxing mechanism&#xA;should be integrated into a program&amp;rsquo;s design: It becomes the&#xA;programmer&amp;rsquo;s responsibility to design the sandbox, and this results in&#xA;narrower sandboxes:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;I should emphasize here that there’s a fundamental difference&#xA;between this project and typical sandboxing projects described in&#xA;the literature. The goal of a typical sandboxing project is to apply&#xA;as many restrictions as possible to a program while receiving no&#xA;help from the programmer; the problem is that this doesn’t stop all&#xA;attacks.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; In contrast, I insist on an extreme sandbox&#xA;guaranteeing complete security, and I then ask how the programmer’s&#xA;time can be minimized.  As in Section 1, programs are not static&#xA;objects; the programmer cooperates with the sandboxing tools.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;networking&#34;&gt;Networking&lt;/h2&gt;&#xA;&lt;p&gt;Related to that, Dan Bernstein has also made the proposal in the past&#xA;that it should be possible to &lt;a href=&#34;https://cr.yp.to/unix/disablenetwork.html&#34;&gt;disable the&#xA;network&lt;/a&gt; with a dedicated&#xA;&lt;code&gt;disablenetwork(void)&lt;/code&gt; function.&lt;/p&gt;&#xA;&lt;p&gt;In 2009-2010, Michael Stone &lt;a href=&#34;https://lore.kernel.org/all/20091227010441.GA12077@heat/&#34;&gt;sent a Linux patchset for&#xA;that&lt;/a&gt;. That&#xA;patchset unfortunately did not make it, but it will hopefully&#xA;eventually be possible with Landlock! &amp;ndash; Mikhail Ivanov&amp;rsquo;s &lt;a href=&#34;https://lore.kernel.org/all/20240904104824.1844082-1-ivanov.mikhail1@huawei-partners.com/&#34;&gt;&amp;ldquo;Support&#xA;socket access-control&amp;rdquo; patch&#xA;set&lt;/a&gt;&#xA;for Landlock should make it possible to forbid new network connections&#xA;in most cases.  (There are tiny exceptions; for the details, see &lt;a href=&#34;https://blog.gnoack.org/post/landlock-ioctl-talk/&#34;&gt;my&#xA;presentation on it&lt;/a&gt;&#xA;starting around minute 34.)&lt;/p&gt;&#xA;&lt;p&gt;I continue to be very excited for this feature to land.  It&amp;rsquo;ll let&#xA;users define reasonably detailed policies for what kinds of networking&#xA;you still want to use, but one of the largest use cases continues to&#xA;be the case where a program absolutely does not want to do &lt;em&gt;any&lt;/em&gt;&#xA;networking.&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;I expect this strategy to produce invulnerable computer systems:&#xA;restructure programs to put almost all code into extreme sandboxes;&#xA;eliminate bugs in the small volume of remaining code. I won’t be&#xA;satisfied until I’ve put the entire security industry out of work.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;We&amp;rsquo;re getting there. 🚀&lt;/p&gt;&#xA;&lt;p&gt;Again, the full writeup from 2005 is available at&#xA;&lt;a href=&#34;https://cr.yp.to/cv/activities-20050107.pdf&#34;&gt;https://cr.yp.to/cv/activities-20050107.pdf&lt;/a&gt; and worth a read.&lt;/p&gt;&#xA;&lt;img src=&#34;/images/sandbox.png&#34;/&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.usenix.org/publications/library/proceedings/sec96/goldberg.html&#34;&gt;https://www.usenix.org/publications/library/proceedings/sec96/goldberg.html&lt;/a&gt;, for example, described a sandboxing tool that applied some restrictions to Netscape’s “DNS helper” program. The subsequently discovered libresolv bug was a security hole in that program despite the sandbox. Imposing heavier restrictions would have meant changing the program.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/djb-sandboxing/</guid>
      <pubDate>Thu, 23 Jan 2025 22:32:27 +0100</pubDate>
    </item>
    <item>
      <title>Upcoming conferences 2025 Q1</title>
      <link>https://blog.gnoack.org/post/upcoming-conferences-2025q1</link>
      <description>&lt;p&gt;I will be attending the following conferences:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://vintagecomputerfestival.ch/&#34;&gt;Vintage Computer Festival Zurich&lt;/a&gt;, January 18-19, 2025&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://fosdem.org/2025/&#34;&gt;FOSDEM 2025&lt;/a&gt;, February 1-2, 2025&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I am &lt;a href=&#34;https://wiki.gnoack.org/FosdemTwentyFive&#34;&gt;collecting a few interesting talks over on the wiki&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Obligatory Landlock talk: &lt;a href=&#34;https://fosdem.org/2025/schedule/event/fosdem-2025-6071-sandbox-ids-with-landlock/&#34;&gt;Sandbox IDs with Landlock&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;We have Landlock stickers this time :)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;If you happen to be at one of these venues,&#xA;if you are interested in sandboxing,&#xA;or if you just want to chat,&#xA;let me know via email. :)&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/upcoming-conferences-2025q1/</guid>
      <pubDate>Fri, 17 Jan 2025 07:44:45 +0100</pubDate>
    </item>
    <item>
      <title>Talk: Update on Landlock: IOCTL support</title>
      <link>https://blog.gnoack.org/post/landlock-ioctl-talk</link>
      <description>&lt;p&gt;📢 I gave a talk about the recent changes in Landlock and its new&#xA;&lt;a href=&#34;https://wiki.gnoack.org/LandlockIoctlControl&#34;&gt;support for restricting IOCTL&#xA;usage&lt;/a&gt; at the Linux&#xA;Security Summit Europe 2024 in Vienna:&lt;/p&gt;&#xA;&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/K2onopkMhuM?si=cywowLOp4-jH-8RB&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;&#xA;&lt;p&gt;🌍 &lt;a href=&#34;https://sched.co/1ebVW&#34;&gt;Talk page&lt;/a&gt;&#xA;| 🎥 &lt;a href=&#34;https://www.youtube.com/watch?v=K2onopkMhuM&#34;&gt;Video on YouTube&lt;/a&gt;&#xA;| 😎 &lt;a href=&#34;https://github.com/landlock-lsm/landlock-logo&#34;&gt;We have stickers!&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;talk-summary&#34;&gt;Talk Summary&lt;/h2&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;The Landlock security module lets Linux processes restrict what they&#xA;can do and puts developers in charge of defining appropriate&#xA;sandboxing policies for their programs. We will give a brief&#xA;overview over Landlock’s current features, recent developments, and&#xA;talk about what is next. We will discuss in more detail Landlock’s&#xA;new support for restricting the use of IOCTL and the design&#xA;considerations and trade-offs that went into it.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;in-other-news&#34;&gt;In other news&lt;/h2&gt;&#xA;&lt;p&gt;I finally took the time to finish up the &lt;a href=&#34;https://wiki.gnoack.org/LandlockFileSystemCompositionModel&#34;&gt;mathematical writeup of how&#xA;Landlock&amp;rsquo;s file system access rights are&#xA;composed&lt;/a&gt;&#xA;on the wiki.  All of this should be obvious from the documentation,&#xA;but it can still be helpful to have a mathematical model to check&#xA;against.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/landlock-ioctl-talk/</guid>
      <pubDate>Sun, 13 Oct 2024 11:15:42 +0200</pubDate>
    </item>
    <item>
      <title>Git: Multirepo patch set</title>
      <link>https://blog.gnoack.org/post/git-multirepo-patch-set</link>
      <description>&lt;p&gt;In the context of &lt;a href=&#34;https://lwn.net/Articles/989215/&#34;&gt;Alejandro Colomar stepping down as a man-pages maintainer&lt;/a&gt; :(, &lt;a href=&#34;https://lwn.net/Articles/989398/&#34;&gt;I learned from him&lt;/a&gt; that&#xA;it is possible with &lt;code&gt;git&lt;/code&gt; to create an email patch set that spans multiple target repositories.&#xA;Specifically, he &lt;a href=&#34;https://lore.kernel.org/linux-man/CAEf4BzZzE94QUdhWPmrMzRBRLa=nm86Mdm5vow688jKq3HzJeA@mail.gmail.com/T/#t&#34;&gt;pointed to a review thread by Jiri Olsa&lt;/a&gt; where this was done, and whose outline takes a similar shape to this (simplified):&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;[PATCH proj v3 0/3]&lt;/strong&gt; foobar: Add transmogrifier&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;[PATCH proj v3 1/3]&lt;/strong&gt; foobar: Prepare flux compensator&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;[PATCH proj v3 2/3]&lt;/strong&gt; foobar: Add transmogrifier feature&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;[PATCH man v3 3/3]&lt;/strong&gt; foobar.1: Document transmogrification&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Other than in normal review threads, &lt;em&gt;every mail subject is prefixed with an additional tag here&lt;/em&gt;, to indicate which project it belongs to.&lt;/p&gt;&#xA;&lt;p&gt;It was not obvious to me how to best achieve it with the existing tools, so I attempted to reconstruct how he had done it, and ended up with the workflow that I will describe below.&#xA;&lt;a href=&#34;https://lore.kernel.org/all/Zt__OKtOj8AZGy4X@krava/&#34;&gt;This is not the same workflow that Jiri Olsa has been using&lt;/a&gt; after all, but it&amp;rsquo;s close, and unusual enough that it&amp;rsquo;s maybe worth documenting here.&lt;/p&gt;&#xA;&lt;h2 id=&#34;1-pull-both-original-repositories-in-the-same-local-repository&#34;&gt;1. Pull both original repositories in the same local repository&lt;/h2&gt;&#xA;&lt;p&gt;We first make sure that all necessary commits are stored in the same local repository, by pulling in a second remote repository with &lt;code&gt;git fetch&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This is unusual, but it is possible.  As a result, your local repository&#xA;contains two unrelated commit histories, for instance one for the linux kernel&#xA;and one for the man pages:&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-0&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:670px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 670.32 211.68&#34;&gt;&#xA;&lt;path d=&#34;M155.16,189.36L515.16,189.36A7.2 7.2 0 0 0 522.36 182.16L522.36,9.36A7.2 7.2 0 0 0 515.16 2.16L155.16,2.16A7.2 7.2 0 0 0 147.96 9.36L147.96,182.16A7.2 7.2 0 0 0 155.16 189.36Z&#34;  style=&#34;fill:none;stroke-width:1.4472;stroke:rgb(0,0,0);stroke-dasharray:7.2,7.2;&#34; /&gt;&#xA;&lt;circle cx=&#34;189.051&#34; cy=&#34;72.0512&#34; r=&#34;14.4&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M203.451,72.0512L228.651,72.0512&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M228.651,72.0512L253.851,72.0512&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;path d=&#34;M253.851,72.0512L279.051,72.0512&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;circle cx=&#34;293.451&#34; cy=&#34;72.0512&#34; r=&#34;14.4&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M293.451,57.6512L293.451,28.8512&#34;  style=&#34;fill:none;stroke-width:1.4472;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M212.451,39.6512L293.451,39.6512L293.451,18.0512L212.451,18.0512Z&#34;  style=&#34;fill:none;stroke-width:1.4472;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;252.951&#34; y=&#34;28.8512&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;linux-base&lt;/text&gt;&#xA;&lt;path d=&#34;M307.851,72.0512L333.051,72.0512&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;circle cx=&#34;347.451&#34; cy=&#34;72.0512&#34; r=&#34;14.4&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M361.851,72.0512L387.051,72.0512&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;circle cx=&#34;401.451&#34; cy=&#34;72.0512&#34; r=&#34;14.4&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M401.451,57.6512L401.451,28.8512&#34;  style=&#34;fill:none;stroke-width:1.4472;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M401.451,39.6512L498.651,39.6512L498.651,18.0512L401.451,18.0512Z&#34;  style=&#34;fill:none;stroke-width:1.4472;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;450.051&#34; y=&#34;28.8512&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;linux-feature&lt;/text&gt;&#xA;&lt;circle cx=&#34;189.051&#34; cy=&#34;158.451&#34; r=&#34;14.4&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M203.451,158.451L228.651,158.451&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M228.651,158.451L253.851,158.451&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;path d=&#34;M253.851,158.451L279.051,158.451&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;circle cx=&#34;293.451&#34; cy=&#34;158.451&#34; r=&#34;14.4&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M293.451,144.051L293.451,115.251&#34;  style=&#34;fill:none;stroke-width:1.4472;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M217.851,126.051L293.451,126.051L293.451,104.451L217.851,104.451Z&#34;  style=&#34;fill:none;stroke-width:1.4472;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;255.651&#34; y=&#34;115.251&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;man-base&lt;/text&gt;&#xA;&lt;path d=&#34;M307.851,158.451L333.051,158.451&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;circle cx=&#34;347.451&#34; cy=&#34;158.451&#34; r=&#34;14.4&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M347.451,144.051L347.451,115.251&#34;  style=&#34;fill:none;stroke-width:1.4472;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M347.451,126.051L439.251,126.051L439.251,104.451L347.451,104.451Z&#34;  style=&#34;fill:none;stroke-width:1.4472;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;393.351&#34; y=&#34;115.251&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;man-feature&lt;/text&gt;&#xA;&lt;path d=&#34;M522.36,72.0512L560.16,72.0512&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M560.16,46.8512L560.16,97.2512A54 10.8 0 0 0 668.16 97.2512L668.16,46.8512A54 10.8 0 0 0 560.16 46.8512A54 10.8 0 0 0 668.16 46.8512&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;614.16&#34; y=&#34;70.0712&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;linux&lt;/text&gt;&#xA;&lt;text x=&#34;614.16&#34; y=&#34;90.2312&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;origin&lt;/text&gt;&#xA;&lt;path d=&#34;M522.36,158.451L560.16,158.451&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M560.16,133.251L560.16,183.651A54 10.8 0 0 0 668.16 183.651L668.16,133.251A54 10.8 0 0 0 560.16 133.251A54 10.8 0 0 0 668.16 133.251&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;614.16&#34; y=&#34;156.471&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;man-pages&lt;/text&gt;&#xA;&lt;text x=&#34;614.16&#34; y=&#34;176.631&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;origin&lt;/text&gt;&#xA;&lt;polygon points=&#34;147.96,72.0512 136.44,76.3712 136.44,67.7312&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M142.2,72.0512L110.16,72.0512&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,97.2512L110.16,97.2512L110.16,46.8512L2.16,46.8512Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;61.9712&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;linux&lt;/text&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;82.1312&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;worktree&lt;/text&gt;&#xA;&lt;polygon points=&#34;147.96,158.451 136.44,162.771 136.44,154.131&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M142.2,158.451L110.16,158.451&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,183.651L110.16,183.651L110.16,133.251L2.16,133.251Z&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;148.371&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;man-pages&lt;/text&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;168.531&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;worktree&lt;/text&gt;&#xA;&lt;text x=&#34;335.16&#34; y=&#34;199.44&#34; text-anchor=&#34;middle&#34; font-style=&#34;italic&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;local repository&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;You can simplify your life here a bit by using &lt;a href=&#34;https://git-scm.com/docs/git-worktree&#34;&gt;git worktrees&lt;/a&gt; &amp;ndash; with these, your can keep doing your Linux work in a separate directory to your man page work, but still keep all of the changes in the same repository.&lt;/p&gt;&#xA;&lt;p&gt;In the example above, we have prepared two commits in the &lt;em&gt;linux-feature&lt;/em&gt; branch, based on the &lt;em&gt;linux-base&lt;/em&gt; branch from upstream, as well as one commit in the &lt;em&gt;man-feature&lt;/em&gt; branch, based on &lt;em&gt;man-base&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;example&#34;&gt;Example&lt;/h3&gt;&#xA;&lt;p&gt;Starting from two repositories &lt;code&gt;/tmp/git/linux-origin&lt;/code&gt; and &lt;code&gt;/tmp/git/man-origin&lt;/code&gt;, let&amp;rsquo;s fetch both repositories into the same local repository and create a worktree for each:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;gnoack:/tmp/git$ git clone /tmp/git/linux-origin linux&#xA;Cloning into &#39;linux&#39;...&#xA;done.&#xA;gnoack:/tmp/git$ cd linux&#xA;gnoack:/tmp/git/linux(main)$ git tag linux-base&#xA;gnoack:/tmp/git/linux(main)$ git remote add man-origin /tmp/git/man-origin&#xA;gnoack:/tmp/git/linux(main)$ git fetch man-origin&#xA;remote: Enumerating objects: 12, done.&#xA;remote: Counting objects: 100% (12/12), done.&#xA;remote: Compressing objects: 100% (4/4), done.&#xA;remote: Total 12 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)&#xA;Unpacking objects: 100% (12/12), 948 bytes | 948.00 KiB/s, done.&#xA;From /tmp/git/man-origin&#xA; * [new branch]      main       -&amp;gt; man-origin/main&#xA;gnoack:/tmp/git/linux(main)$ git worktree add /tmp/git/man man-origin/main&#xA;Preparing worktree (detached HEAD efbfa45)&#xA;HEAD is now at efbfa45 4&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;We now have a worktree for the man pages at &lt;code&gt;/tmp/git/man&lt;/code&gt; and one for Linux at &lt;code&gt;/tmp/git/linux&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;We now prepare the Linux and man page commits on top of these git repositories in the usual way, and make sure that we create branches for the pristine states called &lt;code&gt;man-base&lt;/code&gt; and &lt;code&gt;linux-base&lt;/code&gt;, and call our work branches &lt;code&gt;man-feature&lt;/code&gt; and &lt;code&gt;linux-feature&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;2-format-a-patch-set-that-spans-two-otherwise-unrelated-branches&#34;&gt;2. Format a patch set that spans two otherwise unrelated branches&lt;/h2&gt;&#xA;&lt;p&gt;Create a giant patch set that spans two branches.&#xA;Indicating the desired commit range by using dotted range notation &lt;em&gt;twice&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This invocation is compatible with common flags like &lt;code&gt;-v3&lt;/code&gt; for the patch set version and &lt;code&gt;--cover-letter&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;We use &lt;code&gt;--subject-prefix&lt;/code&gt; so that the mail subject is additionally qualified with an abbreviation for the main project.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;git format-patch -v3 --cover-letter \&#xA;   --subject-prefix=&amp;quot;PATCH proj&amp;quot;    \&#xA;   linux-base..linux-feature        \&#xA;   man-base..man-feature&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Remember from the &lt;a href=&#34;https://man.gnoack.org/7/gitrevisions&#34;&gt;&lt;em&gt;gitrevisions&lt;/em&gt;(7)&lt;/a&gt; man page that a &lt;em&gt;revision range&lt;/em&gt; in Git parlance is a set of commits, which are not necessarily ordered in a single linear chain.  (Somewhat surprisingly, they are also not all connected here, as you would think from reading the man page.)&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;git format-patch&lt;/code&gt; includes some additional features for controlling the ordering, but in practice it seems that commits are ordered by date if they are not connected.&lt;/p&gt;&#xA;&lt;h2 id=&#34;3-final-hand-editing&#34;&gt;3. Final hand-editing&lt;/h2&gt;&#xA;&lt;p&gt;Hand-edit the subject prefix in the man page commit&amp;rsquo;s email, so that the man page commit stands out among the others for the reviewers.&lt;/p&gt;&#xA;&lt;p&gt;And sure enough, after &lt;code&gt;git send-email&lt;/code&gt;, the mail thread has the expected form (in &lt;a href=&#34;http://mutt.org/&#34;&gt;&lt;code&gt;mutt&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt; [PATCH proj v3 0/3] foobar: Add transmogrifier&#xA; ├─&amp;gt;[PATCH proj v3 1/3] foobar: Prepare flux compensator&#xA; ├─&amp;gt;[PATCH proj v3 2/3] foobar: Add transmogrifier feature&#xA; └─&amp;gt;[PATCH man v3 3/3] foobar.1: Document transmogrification&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/git-multirepo-patch-set/</guid>
      <pubDate>Tue, 10 Sep 2024 21:03:55 +0200</pubDate>
    </item>
    <item>
      <title>Upcoming talk at LSS Europe 2024</title>
      <link>https://blog.gnoack.org/post/landlock-ioctl-talk-ann</link>
      <description>&lt;p&gt;📢 I will give a talk about the recent changes in Landlock and its new&#xA;&lt;a href=&#34;https://wiki.gnoack.org/LandlockIoctlControl&#34;&gt;support for restricting IOCTL&#xA;usage&lt;/a&gt; at the Linux&#xA;Security Summit Europe 2024 in Vienna:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;The Landlock security module lets Linux processes restrict what they&#xA;can do and puts developers in charge of defining appropriate&#xA;sandboxing policies for their programs. We will give a brief&#xA;overview over Landlock’s current features, recent developments, and&#xA;talk about what is next. We will discuss in more detail Landlock’s&#xA;new support for restricting the use of IOCTL and the design&#xA;considerations and trade-offs that went into it.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;🌍 Link: &lt;a href=&#34;https://sched.co/1ebVW&#34;&gt;https://sched.co/1ebVW&lt;/a&gt;&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/landlock-ioctl-talk-ann/</guid>
      <pubDate>Thu, 29 Aug 2024 22:33:24 +0200</pubDate>
    </item>
    <item>
      <title>Landlock IOCTL control in Linux 6.10</title>
      <link>https://blog.gnoack.org/post/landlock-ioctl</link>
      <description>&lt;p&gt;&lt;a href=&#34;https://wiki.gnoack.org/LandlockIoctlSupport&#34;&gt;Landlock&amp;rsquo;s IOCTL support for device&#xA;files&lt;/a&gt; has &lt;a href=&#34;https://lwn.net/Articles/981961/&#34;&gt;landed in&#xA;Linux 6.10&lt;/a&gt; 🚀.&lt;/p&gt;&#xA;&lt;p&gt;I also merged &lt;a href=&#34;https://github.com/landlock-lsm/go-landlock/commit/db0c8d6f1dff28841564d98a9ce2e55d644d6530&#34;&gt;the IOCTL support in the Go-Landlock&#xA;library&lt;/a&gt;,&#xA;so you can easily play around with it already.&lt;/p&gt;&#xA;&lt;p&gt;In other news, I started informally documenting Landlock usage in C on&#xA;&lt;a href=&#34;https://wiki.gnoack.org/UsingLandlock&#34;&gt;my wiki on the LandlockUsage&#xA;page&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/landlock-ioctl/</guid>
      <pubDate>Mon, 15 Jul 2024 21:39:21 +0200</pubDate>
    </item>
    <item>
      <title>LLM-generated docs are usually worthless</title>
      <link>https://blog.gnoack.org/post/llm-generated-docs</link>
      <description>&lt;p&gt;This has always been true, but it begs repeating:&lt;/p&gt;&#xA;&lt;blockquote class=&#34;rule&#34;&gt;&lt;p&gt;&lt;strong&gt;Every docstring should provide additional information&lt;/strong&gt;&#xA;about the thing that it is documenting,&#xA;&lt;strong&gt;which is not obvious from the thing&amp;rsquo;s name&lt;/strong&gt; and its type information.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;And the corollary:&lt;/p&gt;&#xA;&lt;blockquote class=&#34;rule&#34;&gt;&lt;p&gt;&lt;strong&gt;If an LLM could generate the same docstring for you&lt;/strong&gt;,&#xA;based on the name and type information alone,&#xA;&lt;strong&gt;then the docstring is not adding any value&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This applies independent of whether it was actually written by a LLM,&#xA;or whether you yourself just mindlessly filled in the blanks without thinking.&lt;/p&gt;&#xA;&lt;p&gt;Writing a docstring is supposed to make you think.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/llm-generated-docs/</guid>
      <pubDate>Fri, 01 Mar 2024 09:09:35 +0100</pubDate>
    </item>
    <item>
      <title>Opening /proc/self/fd/1 is not the same as dup(1)</title>
      <link>https://blog.gnoack.org/post/proc-fd-is-not-dup</link>
      <description>&lt;ul id=&#34;toc&#34;&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#introduction&#34;&gt;Introduction&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#this-feature-is-mis-documented&#34;&gt;This feature is mis-documented&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#devfd-behave-different-on-other-unixes&#34;&gt;/dev/fd/* behave different on other Unixes&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#part-1-an-experiment&#34;&gt;Part 1: An experiment!&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#duplicating-the-file-descriptor-using-dup2&#34;&gt;Duplicating the file descriptor using dup(2)&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#duplicating-the-file-descriptor-through-proc&#34;&gt;Duplicating the file descriptor through /proc&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#other-file-types&#34;&gt;Other file types&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#tcp-sockets-can-not-be-reopened-through-proc&#34;&gt;TCP Sockets: Can not be reopened through /proc&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#pipes-can-be-reopened-through-proc&#34;&gt;Pipes: Can be reopened through /proc&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#part-2-what-is-really-happening&#34;&gt;Part 2: What is really happening&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#where-did-the-no-open-pointer-come-from&#34;&gt;Where did the no_open pointer come from?&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#summary&#34;&gt;Summary&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;Unix processes have &lt;em&gt;file descriptors&lt;/em&gt; which point to &lt;em&gt;file descriptions&lt;/em&gt; (&lt;code&gt;struct file&lt;/code&gt; in Linux).  Multiple file descriptors can point to the same file description, for instance by duplicating them with &lt;a href=&#34;https://man.gnoack.org/2/dup&#34;&gt;&lt;em&gt;dup&lt;/em&gt;(2)&lt;/a&gt;, or by passing them across process boundaries using &lt;a href=&#34;https://man.gnoack.org/2/fork&#34;&gt;&lt;em&gt;fork&lt;/em&gt;(2)&lt;/a&gt; or UNIX Domain Sockets (&lt;a href=&#34;https://man.gnoack.org/7/unix&#34;&gt;&lt;em&gt;unix&lt;/em&gt;(7)&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-0&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:503px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 503.785 218.52&#34;&gt;&#xA;&lt;path d=&#34;M2.16,63.36L110.16,63.36L110.16,27.36L2.16,27.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;45.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;0&lt;/text&gt;&#xA;&lt;path d=&#34;M2.16,99.36L110.16,99.36L110.16,63.36L2.16,63.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;81.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;1&lt;/text&gt;&#xA;&lt;path d=&#34;M2.16,135.36L110.16,135.36L110.16,99.36L2.16,99.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;117.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;2&lt;/text&gt;&#xA;&lt;path d=&#34;M2.16,171.36L110.16,171.36L110.16,135.36L2.16,135.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;153.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;3&lt;/text&gt;&#xA;&lt;path d=&#34;M393.625,63.36L501.625,63.36L501.625,27.36L393.625,27.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;447.625&#34; y=&#34;45.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;0&lt;/text&gt;&#xA;&lt;path d=&#34;M393.625,99.36L501.625,99.36L501.625,63.36L393.625,63.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;447.625&#34; y=&#34;81.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;1&lt;/text&gt;&#xA;&lt;path d=&#34;M393.625,135.36L501.625,135.36L501.625,99.36L393.625,99.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;447.625&#34; y=&#34;117.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;2&lt;/text&gt;&#xA;&lt;path d=&#34;M393.625,171.36L501.625,171.36L501.625,135.36L393.625,135.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;447.625&#34; y=&#34;153.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;3&lt;/text&gt;&#xA;&lt;text x=&#34;2.16&#34; y=&#34;12.24&#34; text-anchor=&#34;start&#34; font-style=&#34;italic&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Process 1:&lt;/text&gt;&#xA;&lt;text x=&#34;393.625&#34; y=&#34;12.24&#34; text-anchor=&#34;start&#34; font-style=&#34;italic&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Process 2:&lt;/text&gt;&#xA;&lt;path d=&#34;M197.892,180.36L305.892,180.36L305.892,126.36L197.892,126.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;143.28&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;file&lt;/text&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;163.44&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;description&lt;/text&gt;&#xA;&lt;path d=&#34;M197.892,216.36L305.892,216.36L305.892,180.36L197.892,180.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;198.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;f_pos&lt;/text&gt;&#xA;&lt;polygon points=&#34;197.892,153.36 186.372,157.68 186.372,149.04&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M110.16,153.36L192.132,153.36&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;polygon points=&#34;305.892,153.36 317.412,149.04 317.412,157.68&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M393.625,153.36L311.652,153.36&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;For a long time, I was under the impression that that was also what happened behind the scenes when opening &lt;code&gt;/dev/fd/${FD}&lt;/code&gt; (a.k.a. &lt;code&gt;/proc/${PID}/fd/${FD}&lt;/code&gt;) on Linux.  I thought I would get a new file descriptor which is also pointing to the same file descrip&lt;em&gt;tion&lt;/em&gt;, similar to if you were calling &lt;code&gt;dup(fd)&lt;/code&gt;.  &lt;strong&gt;This is wrong!&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;this-feature-is-mis-documented&#34;&gt;This feature is mis-documented&lt;/h3&gt;&#xA;&lt;p&gt;The misunderstanding is even documented in my earlier edition of&#xA;&amp;ldquo;&lt;a href=&#34;https://man7.org/tlpi/&#34;&gt;The Linux Programming Interface&lt;/a&gt;&amp;rdquo; (section 5.11)&#xA;(&lt;a href=&#34;https://man7.org/tlpi/errata/index.html#p_107&#34;&gt;but it has been fixed in newer editions&lt;/a&gt;,&#xA;as Michael Kerrisk points out in the comments below):&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Opening one of the files in the /dev/fd directory is equivalent to duplicating the corresponding file descriptor.  Thus, the following statements are equivalent:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-bad&#34;&gt;fd = open(&amp;quot;/dev/fd/1&amp;quot;, O_WRONLY);&#xA;fd = dup(1);&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;This is a reasonably simple explanation which is close enough to&#xA;reality for many practical use cases, and which is true on other&#xA;Unixes, but it is not fully accurate on Linux.  (The book is very&#xA;comprehensive and useful nevertheless.)&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://bugzilla.redhat.com/show_bug.cgi?id=10417&#34;&gt;This RedHat bug from&#xA;2000&lt;/a&gt; discusses how&#xA;that behaviour was apparently changed in Linux 1.3.34.  The&#xA;aforementioned equivalence between the &lt;a href=&#34;https://man.gnoack.org/2/open&#34;&gt;&lt;em&gt;open&lt;/em&gt;(2)&lt;/a&gt; and &lt;a href=&#34;https://man.gnoack.org/2/dup&#34;&gt;&lt;em&gt;dup&lt;/em&gt;(2)&lt;/a&gt; calls is&#xA;called the &amp;ldquo;Plan9 semantics&amp;rdquo; there.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://man.gnoack.org/5/proc_pid_fd&#34;&gt;&lt;em&gt;proc_pid_fd&lt;/em&gt;(5)&lt;/a&gt; gives usage examples, but does not go into a lot of&#xA;detail on the exact semantics in the case of &lt;a href=&#34;https://man.gnoack.org/2/open&#34;&gt;&lt;em&gt;open&lt;/em&gt;(2)&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;devfd-behave-different-on-other-unixes&#34;&gt;&lt;code&gt;/dev/fd/*&lt;/code&gt; behave different on other Unixes&lt;/h3&gt;&#xA;&lt;p&gt;On top of that, the behavior is implemented differently on other Unixes.&lt;/p&gt;&#xA;&lt;p&gt;From a FreeBSD 14 box:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;$ ./dup -dup &amp;gt; out; cat out; echo&#xA;1d&#xA;$ ./dup -proc &amp;gt; out; cat out; echo&#xA;1d&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;On FreeBSD, the result of &lt;code&gt;open(&amp;quot;/dev/fd/1&amp;quot;, O_WRONLY);&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; share the same file descrip&lt;em&gt;tion&lt;/em&gt; with the original file descriptor, as if we were calling &lt;code&gt;dup(1)&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;part-1-an-experiment&#34;&gt;Part 1: An experiment!&lt;/h2&gt;&#xA;&lt;p&gt;It turns out, opening &lt;code&gt;/dev/fd/*&lt;/code&gt;, &lt;code&gt;/proc/${PID}/fd/*&lt;/code&gt; or &lt;code&gt;/proc/self/fd/*&lt;/code&gt; (&lt;a href=&#34;https://man.gnoack.org/5/proc_pid_fd&#34;&gt;&lt;em&gt;proc_pid_fd&lt;/em&gt;(5)&lt;/a&gt;) results in a &lt;em&gt;separate&lt;/em&gt; file descrip&lt;em&gt;tion&lt;/em&gt; (&lt;code&gt;struct file&lt;/code&gt;) being allocated for you, but it refers to the same underlying file on disk.&lt;/p&gt;&#xA;&lt;p&gt;You can try it out with the following program:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;$ cat dup.c&#xA;#include &amp;lt;err.h&amp;gt;&#xA;#include &amp;lt;fcntl.h&amp;gt;&#xA;#include &amp;lt;stdio.h&amp;gt;&#xA;#include &amp;lt;string.h&amp;gt;&#xA;#include &amp;lt;unistd.h&amp;gt;&#xA;&#xA;int usage(const char *name) {&#xA;  printf(&amp;quot;Usage: %s [-dup|-proc]\n&amp;quot;, name);&#xA;  return 0;&#xA;}&#xA;&#xA;int main(int argc, char *argv[]) {&#xA;  int fd;&#xA;&#xA;  if (argc != 2) {&#xA;    return usage(argv[0]);&#xA;  }&#xA;&#xA;  if (!strcmp(argv[1], &amp;quot;-dup&amp;quot;)) {&#xA;    fd = dup(1);  // stdout&#xA;    if (fd &amp;lt; 0) {&#xA;      err(1, &amp;quot;dup&amp;quot;);&#xA;    }&#xA;  } else if (!strcmp(argv[1], &amp;quot;-proc&amp;quot;)) {&#xA;    fd = open(&amp;quot;/dev/fd/1&amp;quot;, O_WRONLY);&#xA;    if (fd &amp;lt; 0) {&#xA;      err(1, &amp;quot;open /dev/fd/1&amp;quot;);&#xA;    }&#xA;  } else {&#xA;    return usage(argv[0]);&#xA;  }&#xA;&#xA;  write(1, &amp;quot;1&amp;quot;, 1);&#xA;  &#xA;  write(fd, &amp;quot;d&amp;quot;, 1);&#xA;  close(fd);&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;When we build and run this program, we can see that the behavior of &lt;a href=&#34;https://man.gnoack.org/2/dup&#34;&gt;&lt;em&gt;dup&lt;/em&gt;(2)&lt;/a&gt; and &lt;a href=&#34;https://man.gnoack.org/2/open&#34;&gt;&lt;em&gt;open&lt;/em&gt;(2)&lt;/a&gt; is actually different!&lt;/p&gt;&#xA;&lt;h3 id=&#34;duplicating-the-file-descriptor-using-dup2&#34;&gt;Duplicating the file descriptor using &lt;a href=&#34;https://man.gnoack.org/2/dup&#34;&gt;&lt;em&gt;dup&lt;/em&gt;(2)&lt;/a&gt;&lt;/h3&gt;&#xA;&lt;pre&gt;&lt;code&gt;$ make dup&#xA;cc -Wall -static    dup.c   -o dup&#xA;$ ./dup -dup &amp;gt; out; cat out; echo&#xA;1d&#xA;$&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;In the &lt;a href=&#34;https://man.gnoack.org/2/dup&#34;&gt;&lt;em&gt;dup&lt;/em&gt;(2)&lt;/a&gt; case, the &lt;code&gt;struct file&lt;/code&gt; is actually shared &amp;ndash; both file descriptors refer to the exact same file descrip&lt;em&gt;tion&lt;/em&gt;.  The first &lt;a href=&#34;https://man.gnoack.org/2/write&#34;&gt;&lt;em&gt;write&lt;/em&gt;(2)&lt;/a&gt; updates the file description&amp;rsquo;s file position (&lt;code&gt;f_pos&lt;/code&gt;).  The second &lt;a href=&#34;https://man.gnoack.org/2/write&#34;&gt;&lt;em&gt;write&lt;/em&gt;(2)&lt;/a&gt; uses the exact same file description, so it sees the updated file position, and the byte gets written &lt;em&gt;after&lt;/em&gt; the one that was written before.&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-1&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:308px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 308.052 173.52&#34;&gt;&#xA;&lt;path d=&#34;M2.16,63.36L110.16,63.36L110.16,27.36L2.16,27.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;45.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;0&lt;/text&gt;&#xA;&lt;path d=&#34;M2.16,99.36L110.16,99.36L110.16,63.36L2.16,63.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;81.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;1&lt;/text&gt;&#xA;&lt;path d=&#34;M2.16,135.36L110.16,135.36L110.16,99.36L2.16,99.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;117.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;2&lt;/text&gt;&#xA;&lt;path d=&#34;M2.16,171.36L110.16,171.36L110.16,135.36L2.16,135.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;153.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;3&lt;/text&gt;&#xA;&lt;text x=&#34;2.16&#34; y=&#34;12.24&#34; text-anchor=&#34;start&#34; font-style=&#34;italic&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;&#39;dup&#39; process:&lt;/text&gt;&#xA;&lt;path d=&#34;M197.892,135.36L305.892,135.36L305.892,81.36L197.892,81.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;98.28&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;file&lt;/text&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;118.44&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;description&lt;/text&gt;&#xA;&lt;path d=&#34;M197.892,171.36L305.892,171.36L305.892,135.36L197.892,135.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;153.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;f_pos&lt;/text&gt;&#xA;&lt;polygon points=&#34;197.892,108.36 185.611,109.1 188.153,100.843&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M110.16,81.36L192.387,106.666&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;polygon points=&#34;197.892,121.86 188.51,129.819 185.59,121.687&#34; style=&#34;fill:rgb(255,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M110.16,153.36L192.471,123.806&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(255,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;text x=&#34;154.026&#34; y=&#34;149.31&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(255,0,0)&#34; transform=&#34;rotate(-19.75051581 154.026,137.61)&#34; dominant-baseline=&#34;central&#34;&gt;dup(2)&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;h3 id=&#34;duplicating-the-file-descriptor-through-proc&#34;&gt;Duplicating the file descriptor through &lt;code&gt;/proc&lt;/code&gt;&lt;/h3&gt;&#xA;&lt;pre&gt;&lt;code&gt;$ ./dup -proc &amp;gt; out; cat out; echo&#xA;d&#xA;$&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;In the &lt;a href=&#34;https://man.gnoack.org/5/proc_pid_fd&#34;&gt;&lt;em&gt;proc_pid_fd&lt;/em&gt;(5)&lt;/a&gt; case, we see only one byte written to the output file.&#xA;So there are &lt;em&gt;two&lt;/em&gt; &lt;code&gt;struct file&lt;/code&gt;s created &amp;ndash;&#xA;and they use independent positions &lt;code&gt;f_pos&lt;/code&gt; in the file, which are both set to 0 initially.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The first &lt;em&gt;write&lt;/em&gt;(2) through stdout (fd 1) updates the file position from 0 to 1.&lt;/li&gt;&#xA;&lt;li&gt;The second &lt;em&gt;write&lt;/em&gt;(2) uses a separate file description&#xA;and &lt;em&gt;overwrites&lt;/em&gt; the byte that was previously written.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;That&amp;rsquo;s why we can only see &amp;ldquo;&lt;code&gt;d&lt;/code&gt;&amp;rdquo; in the output.&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-2&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:308px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 308.052 218.52&#34;&gt;&#xA;&lt;path d=&#34;M2.16,63.36L110.16,63.36L110.16,27.36L2.16,27.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;45.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;0&lt;/text&gt;&#xA;&lt;path d=&#34;M2.16,99.36L110.16,99.36L110.16,63.36L2.16,63.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;81.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;1&lt;/text&gt;&#xA;&lt;path d=&#34;M2.16,135.36L110.16,135.36L110.16,99.36L2.16,99.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;117.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;2&lt;/text&gt;&#xA;&lt;path d=&#34;M2.16,171.36L110.16,171.36L110.16,135.36L2.16,135.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;153.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;3&lt;/text&gt;&#xA;&lt;text x=&#34;2.16&#34; y=&#34;12.24&#34; text-anchor=&#34;start&#34; font-style=&#34;italic&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;&#39;dup&#39; process:&lt;/text&gt;&#xA;&lt;path d=&#34;M197.892,80.0135L305.892,80.0135L305.892,26.0135L197.892,26.0135Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;42.9335&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;file&lt;/text&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;63.0935&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;description&lt;/text&gt;&#xA;&lt;path d=&#34;M197.892,116.014L305.892,116.014L305.892,80.0135L197.892,80.0135Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;98.0135&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;f_pos&lt;/text&gt;&#xA;&lt;path d=&#34;M197.892,180.36L305.892,180.36L305.892,126.36L197.892,126.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;143.28&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;file&lt;/text&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;163.44&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;description&lt;/text&gt;&#xA;&lt;path d=&#34;M197.892,216.36L305.892,216.36L305.892,180.36L197.892,180.36Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;251.892&#34; y=&#34;198.36&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;f_pos&lt;/text&gt;&#xA;&lt;polygon points=&#34;197.892,53.0135 188.258,60.6661 185.602,52.4446&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M110.16,81.36L192.411,54.7845&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;polygon points=&#34;197.892,153.36 186.372,157.68 186.372,149.04&#34; style=&#34;fill:rgb(255,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M110.16,153.36L192.132,153.36&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(255,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;text x=&#34;154.026&#34; y=&#34;165.06&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(255,0,0)&#34; transform=&#34;rotate(-0 154.026,153.36)&#34; dominant-baseline=&#34;central&#34;&gt;open(2)&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;h3 id=&#34;other-file-types&#34;&gt;Other file types&lt;/h3&gt;&#xA;&lt;p&gt;So far, this was a bit confusing.  It&amp;rsquo;s definitely inconsistent with&#xA;the theory that opening &lt;code&gt;/dev/fd/*&lt;/code&gt; does the same as &lt;a href=&#34;https://man.gnoack.org/2/dup&#34;&gt;&lt;em&gt;dup&lt;/em&gt;(2)&lt;/a&gt;.  But&#xA;what happens for other file types than regular files?&lt;/p&gt;&#xA;&lt;h3 id=&#34;tcp-sockets-can-not-be-reopened-through-proc&#34;&gt;TCP Sockets: Can not be reopened through &lt;code&gt;/proc&lt;/code&gt;&lt;/h3&gt;&#xA;&lt;p&gt;You can try this out by redirecting stdout to a socket, using the&#xA;obscure &lt;code&gt;/dev/tcp&lt;/code&gt; extension in bash&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;$ nc -l 9999 &amp;amp;&#xA;[1] 4166&#xA;$ ./dup -proc &amp;gt;/dev/tcp/localhost/9999&#xA;dup: open /dev/fd/1: No such device or address&#xA;[1]+  Done                    nc -l 9999&#xA;$ &#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The error here is &lt;code&gt;ENXIO: No such device or address&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;For sockets, the &lt;code&gt;/proc/self/fd/*&lt;/code&gt; entry is a symlink to a name like &lt;code&gt;socket:[16902]&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;lrwx------ 1 gnoack gnoack 64 Feb 17 23:12 1 -&amp;gt; &#39;socket:[16902]&#39;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h3 id=&#34;pipes-can-be-reopened-through-proc&#34;&gt;Pipes: &lt;em&gt;Can&lt;/em&gt; be reopened through &lt;code&gt;/proc&lt;/code&gt;&lt;/h3&gt;&#xA;&lt;p&gt;However, a pipe &lt;strong&gt;can&lt;/strong&gt; be reopened through &lt;code&gt;/dev/fd/1&lt;/code&gt;, for example like this:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;$ ./dup -proc | cat ; echo&#xA;1d&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&amp;hellip;and this works even though the pipe&amp;rsquo;s symlink looks like this:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;l-wx------ 1 gnoack gnoack 64 Feb 17 23:10 1 -&amp;gt; &#39;pipe:[15895]&#39;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;part-2-what-is-really-happening&#34;&gt;Part 2: What is really happening&lt;/h2&gt;&#xA;&lt;p&gt;First, let&amp;rsquo;s recall the in-kernel VFS structure:&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-3&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:652px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 652.32 337.32&#34;&gt;&#xA;&lt;path d=&#34;M2.16,164.16L110.16,164.16L110.16,128.16L2.16,128.16Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;146.16&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Process&lt;/text&gt;&#xA;&lt;polygon points=&#34;182.16,146.16 170.64,150.48 170.64,141.84&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M110.16,146.16L176.4,146.16&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;146.16&#34; y=&#34;134.46&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;fd&lt;/text&gt;&#xA;&lt;path d=&#34;M197.16,164.16L275.16,164.16A15 15 0 0 0 290.16 149.16L290.16,143.16A15 15 0 0 0 275.16 128.16L197.16,128.16A15 15 0 0 0 182.16 143.16L182.16,149.16A15 15 0 0 0 197.16 164.16Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;236.16&#34; y=&#34;146.16&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;file object&lt;/text&gt;&#xA;&lt;polygon points=&#34;362.16,146.16 350.64,150.48 350.64,141.84&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M290.16,146.16L356.4,146.16&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;326.16&#34; y=&#34;134.46&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;f_path&lt;/text&gt;&#xA;&lt;path d=&#34;M362.16,164.16L470.16,164.16L470.16,128.16L362.16,128.16Z&#34;  style=&#34;fill:rgb(255,250,205);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;416.16&#34; y=&#34;146.16&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;path&lt;/text&gt;&#xA;&lt;polygon points=&#34;542.16,146.16 530.64,150.48 530.64,141.84&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M470.16,146.16L536.4,146.16&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;506.16&#34; y=&#34;134.46&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;dentry&lt;/text&gt;&#xA;&lt;path d=&#34;M542.16,164.16L650.16,164.16L650.16,128.16L542.16,128.16Z&#34;  style=&#34;fill:rgb(176,224,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;596.16&#34; y=&#34;146.16&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;dentry&lt;/text&gt;&#xA;&lt;polygon points=&#34;596.16,236.16 591.84,224.64 600.48,224.64&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M596.16,164.16L596.16,230.4&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;596.16&#34; y=&#34;188.46&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; transform=&#34;rotate(90 596.16,200.16)&#34; dominant-baseline=&#34;central&#34;&gt;d_inode&lt;/text&gt;&#xA;&lt;path d=&#34;M557.16,272.16L635.16,272.16A15 15 0 0 0 650.16 257.16L650.16,251.16A15 15 0 0 0 635.16 236.16L557.16,236.16A15 15 0 0 0 542.16 251.16L542.16,257.16A15 15 0 0 0 557.16 272.16Z&#34;  style=&#34;fill:rgb(255,239,213);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;596.16&#34; y=&#34;254.16&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;inode object&lt;/text&gt;&#xA;&lt;polygon points=&#34;470.16,254.16 481.68,249.84 481.68,258.48&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M542.16,254.16L475.92,254.16&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;506.16&#34; y=&#34;242.46&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;i_sb&lt;/text&gt;&#xA;&lt;path d=&#34;M362.16,281.16L470.16,281.16L470.16,227.16L362.16,227.16Z&#34;  style=&#34;fill:rgb(255,160,122);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;416.16&#34; y=&#34;244.08&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Superblock&lt;/text&gt;&#xA;&lt;text x=&#34;416.16&#34; y=&#34;264.24&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;object&lt;/text&gt;&#xA;&lt;polygon points=&#34;290.16,254.16 301.68,249.84 301.68,258.48&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M362.16,254.16L295.92,254.16&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;polygon points=&#34;290.16,308.16 301.68,303.84 301.68,312.48&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M596.16,272.16L596.16,308.16L295.92,308.16&#34;  style=&#34;fill:none;stroke-width:2.16;stroke-linejoin:round;stroke:rgb(0,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;path d=&#34;M182.16,237.96L182.16,324.36A54 10.8 0 0 0 290.16 324.36L290.16,237.96A54 10.8 0 0 0 182.16 237.96A54 10.8 0 0 0 290.16 237.96&#34;  style=&#34;fill:rgb(219,112,147);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;236.16&#34; y=&#34;279.18&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Disk&lt;/text&gt;&#xA;&lt;text x=&#34;236.16&#34; y=&#34;299.34&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;file&lt;/text&gt;&#xA;&lt;polygon points=&#34;416.16,56.16 420.48,67.68 411.84,67.68&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M416.16,128.16L416.16,61.92&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;416.16&#34; y=&#34;80.46&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; transform=&#34;rotate(-90 416.16,92.16)&#34; dominant-baseline=&#34;central&#34;&gt;mnt&lt;/text&gt;&#xA;&lt;path d=&#34;M362.16,56.16L470.16,56.16L470.16,2.16L362.16,2.16Z&#34;  style=&#34;fill:rgb(255,215,0);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;416.16&#34; y=&#34;19.08&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;struct&lt;/text&gt;&#xA;&lt;text x=&#34;416.16&#34; y=&#34;39.24&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;vfsmount&lt;/text&gt;&#xA;&lt;text x=&#34;236.16&#34; y=&#34;92.88&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;This is the&lt;/text&gt;&#xA;&lt;text x=&#34;236.16&#34; y=&#34;113.04&#34; text-anchor=&#34;middle&#34; font-style=&#34;italic&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;file description&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;The following things happen in a sequence:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;A user space process calls &lt;code&gt;open(&amp;quot;/proc/self/fd/1&amp;quot;)&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;System call handler:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;parses flags&lt;/li&gt;&#xA;&lt;li&gt;does the path walk, which eventually invokes &lt;code&gt;proc_pid_get_link()&lt;/code&gt;:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;fs/proc/base.c:proc_pid_get_link()&lt;/code&gt;:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;invokes &lt;code&gt;proc_fd_link()&lt;/code&gt; through a callback&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;fs/proc/fd.c:proc_fd_link()&lt;/code&gt;: looks up the original &lt;code&gt;struct file*&lt;/code&gt; from the target task and &lt;strong&gt;returns the &lt;code&gt;-&amp;gt;f_path&lt;/code&gt;&lt;/strong&gt; that existed on that &lt;code&gt;struct file&lt;/code&gt; (through an output pointer argument).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;invokes &lt;code&gt;nd_jump_link()&lt;/code&gt;, which &lt;strong&gt;sets the result of the path walk in &lt;code&gt;nameidata&lt;/code&gt; to the previously set path!&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;eventually calls &lt;code&gt;path_openat()&lt;/code&gt;.&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;namei.c:path_openat()&lt;/code&gt;: &lt;strong&gt;Always&lt;/strong&gt; allocates a new &lt;code&gt;struct file&lt;/code&gt; with &lt;code&gt;alloc_empty_file()&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;namei.c:do_open()&lt;/code&gt;: calls &lt;code&gt;vfs_open()&lt;/code&gt;, which in turn calls &lt;code&gt;do_dentry_open()&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;open.c:do_dentry_open()&lt;/code&gt;:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;first initializes the file ops from the inode: &lt;code&gt;f-&amp;gt;f_op = fops_get(inode-&amp;gt;i_fop)&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;the calls the &amp;ldquo;open&amp;rdquo; file operation: &lt;code&gt;f-&amp;gt;f_op-&amp;gt;open&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;where-did-the-no-open-pointer-come-from&#34;&gt;Where did the &lt;code&gt;no_open&lt;/code&gt; pointer come from?&lt;/h3&gt;&#xA;&lt;p&gt;For the TCP socket above, &lt;code&gt;f-&amp;gt;f_op-&amp;gt;open&lt;/code&gt; is set to the &lt;code&gt;no_open&lt;/code&gt; function, which unconditionally returns &lt;code&gt;ENXIO&lt;/code&gt;.  So that socket can&amp;rsquo;t be reopened through &lt;code&gt;/proc&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;https://blog.gnoack.org/images/perf-no_open.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;The decision which &lt;code&gt;f_op-&amp;gt;open&lt;/code&gt; is used for each file is done in &lt;code&gt;inode.c:init_special_inode&lt;/code&gt;, for sockets and pipes.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Every call to &lt;a href=&#34;https://man.gnoack.org/2/open&#34;&gt;&lt;em&gt;open&lt;/em&gt;(2)&lt;/a&gt; results in a new &lt;code&gt;struct file*&lt;/code&gt; being allocated.&lt;/li&gt;&#xA;&lt;li&gt;The resulting &lt;code&gt;struct file*&lt;/code&gt; refers to an existing inode, even for special files like pipes.&lt;/li&gt;&#xA;&lt;li&gt;Not all of the special files support this kind of re-opening.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;These &lt;code&gt;/dev/tcp/...&lt;/code&gt; files do not actually exist: bash treats these&#xA;paths specially and really just calls the BSD socket API&#xA;itself&amp;hellip; but we can use it here to write directly into a socket.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/proc-fd-is-not-dup/</guid>
      <pubDate>Mon, 19 Feb 2024 20:45:12 +0100</pubDate>
    </item>
    <item>
      <title>Go-Landlock: Networking support</title>
      <link>https://blog.gnoack.org/post/landlock-v4</link>
      <description>&lt;img src=&#34;https://blog.gnoack.org/images/go-landlock-logo.svg&#34; style=&#34;float: right; max-width: 20%;&#34;/&gt;&#xA;&lt;p&gt;In Linux 6.7, Konstantin Meskhidze introduced TCP Networking support&#xA;for Landlock. 🚀 I am happy to announce that Go-Landlock is one of the&#xA;first libraries using it, and it has a demo tool.&lt;/p&gt;&#xA;&lt;h2 id=&#34;demo&#34;&gt;Demo&lt;/h2&gt;&#xA;&lt;p&gt;If you are running Linux 6.7 or higher and have Landlock enabled, you can try it out like this:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Install the tool to &lt;code&gt;~/go/bin&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;go install github.com/landlock-lsm/go-landlock/cmd/landlock-restrict-net@latest&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Invoke the tool using:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;landlock-restrict-net -tcp.bind 8080 /usr/bin/nc -l 127.0.0.1 8080&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This invokes the command &lt;code&gt;nc -l 127.0.0.1 8080&lt;/code&gt; under a Landlock&#xA;policy where 8080 is the only TCP port which can be bound with&#xA;&lt;a href=&#34;https://man7.org/linux/man-pages/man2/bind.2.html&#34;&gt;&lt;em&gt;bind&lt;/em&gt;(2)&lt;/a&gt;.&#xA;The command listens on TCP port 8080, so this works.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;You can see the effect of the sandbox by changing one of the two port numbers:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;landlock-restrict-net -tcp.bind 8081 /usr/bin/nc -l 127.0.0.1 8080&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;In this case, the &lt;code&gt;nc&lt;/code&gt; command will fail to listen on 8080, because&#xA;this port can not be bound.  The sandbox works. 🎉&lt;/p&gt;&#xA;&lt;h2 id=&#34;go-api&#34;&gt;Go API&lt;/h2&gt;&#xA;&lt;p&gt;Landlock is not only useful to sandbox other programs from the&#xA;outside, but it shines when programs sandbox themselves.&lt;/p&gt;&#xA;&lt;p&gt;The Go API for Go-Landlock&amp;rsquo;s networking support is in line with the&#xA;API that Go-Landlock already provides for file system restrictions:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;err := landlock.V4.BestEffort().RestrictNet(&#xA;    landlock.BindTCP(8080),&#xA;    landlock.ConnectTCP(25),&#xA;    landlock.ConnectTCP(587),&#xA;)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This will enable a Landlock policy on the calling process, where:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;only TCP port 8080 can be bound with&#xA;&lt;a href=&#34;https://man7.org/linux/man-pages/man2/bind.2.html&#34;&gt;&lt;em&gt;bind&lt;/em&gt;(2)&lt;/a&gt;, and&lt;/li&gt;&#xA;&lt;li&gt;only TCP ports 25 and 587 can be connected to with&#xA;&lt;a href=&#34;https://man7.org/linux/man-pages/man2/connect.2.html&#34;&gt;&lt;em&gt;connect&lt;/em&gt;(2)&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The &lt;code&gt;RestrictNet()&lt;/code&gt; method is a new addition next to the previously&#xA;existing &lt;code&gt;RestrictPaths()&lt;/code&gt;.  These methods restrict &lt;em&gt;either&lt;/em&gt;&#xA;networking &lt;em&gt;or&lt;/em&gt; file system accesses.&lt;/p&gt;&#xA;&lt;p&gt;The other new addition is the &lt;a href=&#34;https://pkg.go.dev/github.com/landlock-lsm/go-landlock/landlock#hdr-Restricting_file_access_and_networking_at_once&#34;&gt;generic &lt;code&gt;Restrict()&lt;/code&gt;&#xA;method&lt;/a&gt;,&#xA;which restricts both networking and file system access at the same&#xA;time.&lt;/p&gt;&#xA;&lt;p&gt;Non-TCP protocols are currently unaffected by Landlock.  In&#xA;particular, UDP can still be freely used independent of any enabled&#xA;Landlock policies.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/landlock-v4/</guid>
      <pubDate>Wed, 10 Jan 2024 21:23:00 +0100</pubDate>
    </item>
    <item>
      <title>Public tweets are not public, haha!</title>
      <link>https://blog.gnoack.org/post/social-media</link>
      <description>&lt;h2 id=&#34;twitter&#34;&gt;Twitter&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Posting: You can tweet, but the public can&amp;rsquo;t read it&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Reading: Most of my timeline is garbage.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;mastodon&#34;&gt;Mastodon&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Posting: Everything is quasi-public, and instance operators are all-knowing gods&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Reading: &lt;em&gt;All&lt;/em&gt; of my timeline is garbage.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I&amp;rsquo;m sad for Mastodon. It would have been an opportunity to reverse&#xA;the attention-grabbing engagement-through-outrage mechanisms,&#xA;which have already annoyed me on other social media platforms.&#xA;Somehow this did not work out.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-blogosphere&#34;&gt;The Blogosphere&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Posting: Everything is public, and it does not try to pretend otherwise.&lt;/li&gt;&#xA;&lt;li&gt;Reading: Readers control what they are reading through their RSS subscriptions.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;And this is the winner.&lt;/p&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I&amp;rsquo;m ditching Twitter.&lt;/li&gt;&#xA;&lt;li&gt;I&amp;rsquo;m not joining Mastodon.&lt;/li&gt;&#xA;&lt;li&gt;I keep operating this trusty weblog.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;If you don&amp;rsquo;t have one yet, &lt;em&gt;get yourself an RSS Reader app&lt;/em&gt;,&#xA;and &lt;em&gt;subscribe via RSS&lt;/em&gt;! (see link at the top)&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Yes! Reading posts requires a Twitter account now!&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;Guarantees are difficult to make in a federated environment.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/social-media/</guid>
      <pubDate>Wed, 12 Jul 2023 20:38:02 +0200</pubDate>
    </item>
    <item>
      <title>The Design of Mailprint</title>
      <link>https://blog.gnoack.org/post/mailprint-design</link>
      <description>&lt;p&gt;I wrote a tool, &lt;a href=&#34;https://gnoack.github.io/mailprint/&#34;&gt;&lt;em&gt;Mailprint&lt;/em&gt;&lt;/a&gt;,&#xA;for printing out nice-looking emails from &lt;a href=&#34;http://www.mutt.org/&#34;&gt;Mutt&lt;/a&gt;&#xA;and other classic Mail user agents.&lt;/p&gt;&#xA;&lt;p&gt;(You might remember from the &lt;a href=&#34;https://blog.gnoack.org/post/lei/&#34;&gt;last article&lt;/a&gt;&#xA;that I have recently spent some time to polish my e-mail setup for kernel development.&#xA;While this is not the hip thing to do any more,&#xA;some of those mails are difficult to understand,&#xA;and I occasionally &lt;em&gt;print them out&lt;/em&gt;.)&lt;/p&gt;&#xA;&lt;p&gt;This article describes &lt;em&gt;Mailprint&lt;/em&gt;&amp;rsquo;s internals and design philosophy.&#xA;I&amp;rsquo;m fond of this approach, because it fits nicely into the UNIX environment and is reusable.&lt;/p&gt;&#xA;&lt;p&gt;If you are interested in &lt;em&gt;using&lt;/em&gt; &lt;em&gt;Mailprint&lt;/em&gt;, you can find its homepage at &lt;a href=&#34;https://gnoack.github.io/mailprint/&#34;&gt;https://gnoack.github.io/mailprint/&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;design-philosophy&#34;&gt;Design philosophy&lt;/h2&gt;&#xA;&lt;p&gt;Orthogonal software design, in the UNIX-style,&#xA;building one tool for each task, is great.&#xA;This applies in particular to side-projects,&#xA;because it is a way to achieve a lot more with less work.&lt;/p&gt;&#xA;&lt;p&gt;Mailprint is a tool which could have existed in similar form in the 80s already.&lt;/p&gt;&#xA;&lt;p&gt;It makes use of a pipeline of other tools to do its job:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;GNU troff (&lt;code&gt;groff&lt;/code&gt;) to format pages&lt;/li&gt;&#xA;&lt;li&gt;ImageMagick&amp;rsquo;s &lt;code&gt;convert&lt;/code&gt; to convert profile pictures from various image formats&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;interface-to-the-outside&#34;&gt;Interface to the outside&lt;/h3&gt;&#xA;&lt;p&gt;&lt;strong&gt;To the outside,&lt;/strong&gt;&#xA;Mailprint is designed to be used as part of a UNIX pipeline,&#xA;reading a plain text email and outputting PDF:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;cat ~/.Mail/Inbox/cur/foobar123 | mailprint &amp;gt; out.pdf&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div id=&#39;pikchr-0&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:260px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 260.64 76.32&#34;&gt;&#xA;&lt;polygon points=&#34;74.16,38.16 62.64,42.48 62.64,33.84&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M2.16,38.16L68.4,38.16&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;38.16&#34; y=&#34;26.46&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;email&lt;/text&gt;&#xA;&lt;path d=&#34;M74.16,74.16L182.16,74.16L182.16,2.16L74.16,2.16Z&#34;  style=&#34;fill:rgb(255,255,224);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;128.16&#34; y=&#34;38.16&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;mailprint&lt;/text&gt;&#xA;&lt;polygon points=&#34;254.16,38.16 242.64,42.48 242.64,33.84&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M182.16,38.16L248.4,38.16&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;218.16&#34; y=&#34;26.46&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;PDF&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;This makes it easy to hook up Mailprint to &lt;code&gt;mutt&lt;/code&gt; and other classic MUAs.&lt;/p&gt;&#xA;&lt;h3 id=&#34;internal-design&#34;&gt;Internal design&lt;/h3&gt;&#xA;&lt;blockquote class=&#34;warning&#34;&gt;&lt;p&gt;⚠️ &lt;strong&gt;This section is not up-to-date any more.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;More recent versions of Mailprint generate PDF directly with a PDF generation library&#xA;and do not start additional child processes.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Internally, Mailprint generates &lt;a href=&#34;http://www.schaffter.ca/mom/momdoc/toc.html&#34;&gt;groff source code in the &amp;ldquo;mom&amp;rdquo; dialect&lt;/a&gt; from the input email and feeds it through a UNIX pipeline of processing tools:&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-1&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:980px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 980.64 236.336&#34;&gt;&#xA;&lt;path d=&#34;M93.1521,234.176L883.168,234.176L883.168,2.16L93.1521,2.16Z&#34;  style=&#34;fill:rgb(255,255,224);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;110.16,181.168 98.64,185.488 98.64,176.848&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M2.16,181.168L104.4,181.168&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;169.468&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;email&lt;/text&gt;&#xA;&lt;path d=&#34;M110.16,217.168L218.16,217.168L218.16,145.168L110.16,145.168Z&#34;  style=&#34;fill:rgb(255,228,196);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;164.16&#34; y=&#34;181.168&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;parse email&lt;/text&gt;&#xA;&lt;polygon points=&#34;326.16,181.168 314.64,185.488 314.64,176.848&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M218.16,181.168L320.4,181.168&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;272.16&#34; y=&#34;169.468&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;headers,&lt;/text&gt;&#xA;&lt;text x=&#34;272.16&#34; y=&#34;192.868&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;body&lt;/text&gt;&#xA;&lt;path d=&#34;M326.16,217.168L434.16,217.168L434.16,145.168L326.16,145.168Z&#34;  style=&#34;fill:rgb(255,228,196);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;380.16&#34; y=&#34;161.008&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;convert&lt;/text&gt;&#xA;&lt;text x=&#34;380.16&#34; y=&#34;181.168&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;email&lt;/text&gt;&#xA;&lt;text x=&#34;380.16&#34; y=&#34;201.328&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;to groff&lt;/text&gt;&#xA;&lt;polygon points=&#34;542.16,181.168 530.64,185.488 530.64,176.848&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M434.16,181.168L536.4,181.168&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;488.16&#34; y=&#34;169.468&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;mom&lt;/text&gt;&#xA;&lt;text x=&#34;488.16&#34; y=&#34;192.868&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;(groff&lt;/text&gt;&#xA;&lt;text x=&#34;488.16&#34; y=&#34;213.028&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;source)&lt;/text&gt;&#xA;&lt;path d=&#34;M542.16,217.168L650.16,217.168L650.16,145.168L542.16,145.168Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;596.16&#34; y=&#34;181.168&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;preconv&lt;/text&gt;&#xA;&lt;polygon points=&#34;758.16,181.168 746.64,185.488 746.64,176.848&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M650.16,181.168L752.4,181.168&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;704.16&#34; y=&#34;169.468&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;mom&lt;/text&gt;&#xA;&lt;text x=&#34;704.16&#34; y=&#34;192.868&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;(sanitized&lt;/text&gt;&#xA;&lt;text x=&#34;704.16&#34; y=&#34;213.028&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Unicode)&lt;/text&gt;&#xA;&lt;path d=&#34;M758.16,217.168L866.16,217.168L866.16,145.168L758.16,145.168Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;812.16&#34; y=&#34;171.088&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;groff&lt;/text&gt;&#xA;&lt;text x=&#34;812.16&#34; y=&#34;191.248&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;-mom -Tpdf&lt;/text&gt;&#xA;&lt;polygon points=&#34;974.16,181.168 962.64,185.488 962.64,176.848&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M866.16,181.168L968.4,181.168&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;920.16&#34; y=&#34;169.468&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;PDF&lt;/text&gt;&#xA;&lt;path d=&#34;M164.16,145.168L164.16,73.1679&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;164.16&#34; y=&#34;97.4679&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; transform=&#34;rotate(-90 164.16,109.168)&#34; dominant-baseline=&#34;central&#34;&gt;sender&lt;/text&gt;&#xA;&lt;text x=&#34;164.16&#34; y=&#34;120.868&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; transform=&#34;rotate(-90 164.16,109.168)&#34; dominant-baseline=&#34;central&#34;&gt;address&lt;/text&gt;&#xA;&lt;polygon points=&#34;218.16,73.1679 206.64,77.4879 206.64,68.8479&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M164.16,73.1679L212.4,73.1679&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M218.16,109.168L326.16,109.168L326.16,37.1679L218.16,37.1679Z&#34;  style=&#34;fill:rgb(255,228,196);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;272.16&#34; y=&#34;63.0879&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;look up&lt;/text&gt;&#xA;&lt;text x=&#34;272.16&#34; y=&#34;83.2479&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;face&lt;/text&gt;&#xA;&lt;polygon points=&#34;434.16,73.1679 422.64,77.4879 422.64,68.8479&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M326.16,73.1679L428.4,73.1679&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;380.16&#34; y=&#34;61.4679&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;filename&lt;/text&gt;&#xA;&lt;path d=&#34;M434.16,109.168L542.16,109.168L542.16,37.1679L434.16,37.1679Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;488.16&#34; y=&#34;63.0879&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;ImageMagick&lt;/text&gt;&#xA;&lt;text x=&#34;488.16&#34; y=&#34;83.2479&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;convert&lt;/text&gt;&#xA;&lt;polygon points=&#34;650.16,73.1679 638.64,77.4879 638.64,68.8479&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M542.16,73.1679L644.4,73.1679&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M650.16,127.168L722.16,127.168L722.16,40.7679L700.56,19.1679L650.16,19.1679Z&#34;  style=&#34;fill:rgb(255,255,255);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M700.56,19.1679L700.56,40.7679L722.16,40.7679&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;686.16&#34; y=&#34;63.0879&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;PDF&lt;/text&gt;&#xA;&lt;text x=&#34;686.16&#34; y=&#34;83.2479&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;file&lt;/text&gt;&#xA;&lt;polygon points=&#34;812.16,145.168 807.84,133.648 816.48,133.648&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M722.16,73.1679L812.16,73.1679L812.16,139.408&#34;  style=&#34;fill:none;stroke-width:2.16;stroke-linejoin:round;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;874.664&#34; y=&#34;12.24&#34; text-anchor=&#34;end&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;mailprint&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;The steps implemented in Go are:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Parsing emails&lt;/strong&gt; into email headers and bodies. (&lt;a href=&#34;https://github.com/gnoack/mailprint/blob/main/parse.go&#34;&gt;code&lt;/a&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Looking up the sender&amp;rsquo;s profile picture:&lt;/strong&gt;&#xA;This is also a separate Go library, &lt;a href=&#34;https://github.com/gnoack/picon&#34;&gt;picon&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Converting the email to groff source code&lt;/strong&gt;&#xA;&lt;a href=&#34;http://www.schaffter.ca/mom/momdoc/toc.html&#34;&gt;of the &amp;ldquo;mom&amp;rdquo; flavor&lt;/a&gt;,&#xA;which is the heart of the program. (&lt;a href=&#34;https://github.com/gnoack/mailprint/blob/main/render.go&#34;&gt;code&lt;/a&gt;)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The remaining steps are accomplished by invoking external UNIX programs:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;ImageMagick &lt;code&gt;convert&lt;/code&gt;&lt;/strong&gt; converts the discovered profile pictures from various source formats into the PDF format.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;preconv&lt;/code&gt;&lt;/strong&gt; belongs to the groff suite and converts Unicode characters into input that GNU troff understands.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;groff&lt;/code&gt;&lt;/strong&gt; finally creates the PDF from the input source.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The pipeline is invoked from Mailprint&amp;rsquo;s top-level &lt;code&gt;run()&lt;/code&gt; function. (&lt;a href=&#34;https://github.com/gnoack/mailprint/blob/main/cmd/mailprint/main.go#L127&#34;&gt;code&lt;/a&gt;)&lt;/p&gt;&#xA;&lt;h3 id=&#34;turning-it-inside-out&#34;&gt;Turning it inside-out&lt;/h3&gt;&#xA;&lt;p&gt;If you want to build a more custom Mailprint pipeline, you can also make Mailprint expose its groff intermediary format using the &lt;code&gt;-output.format=mom&lt;/code&gt; option, and chain it with groff yourself:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;mailprint -face.picon=false -output.format=mom | preconv | groff -mom -Tpdf | lpr&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div id=&#39;pikchr-2&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:980px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 980.64 127.344&#34;&gt;&#xA;&lt;path d=&#34;M93.1521,125.184L451.168,125.184L451.168,2.16L93.1521,2.16Z&#34;  style=&#34;fill:rgb(255,255,224);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;110.16,72.1757 98.64,76.4957 98.64,67.8557&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M2.16,72.1757L104.4,72.1757&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.16&#34; y=&#34;60.4757&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;email&lt;/text&gt;&#xA;&lt;path d=&#34;M110.16,108.176L218.16,108.176L218.16,36.1757L110.16,36.1757Z&#34;  style=&#34;fill:rgb(255,228,196);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;164.16&#34; y=&#34;72.1757&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;parse email&lt;/text&gt;&#xA;&lt;polygon points=&#34;326.16,72.1757 314.64,76.4957 314.64,67.8557&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M218.16,72.1757L320.4,72.1757&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;272.16&#34; y=&#34;60.4757&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;headers,&lt;/text&gt;&#xA;&lt;text x=&#34;272.16&#34; y=&#34;83.8757&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;body&lt;/text&gt;&#xA;&lt;path d=&#34;M326.16,108.176L434.16,108.176L434.16,36.1757L326.16,36.1757Z&#34;  style=&#34;fill:rgb(255,228,196);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;380.16&#34; y=&#34;52.0157&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;convert&lt;/text&gt;&#xA;&lt;text x=&#34;380.16&#34; y=&#34;72.1757&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;email&lt;/text&gt;&#xA;&lt;text x=&#34;380.16&#34; y=&#34;92.3357&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;to groff&lt;/text&gt;&#xA;&lt;polygon points=&#34;542.16,72.1757 530.64,76.4957 530.64,67.8557&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M434.16,72.1757L536.4,72.1757&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;488.16&#34; y=&#34;60.4757&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;mom&lt;/text&gt;&#xA;&lt;text x=&#34;488.16&#34; y=&#34;83.8757&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;(groff&lt;/text&gt;&#xA;&lt;text x=&#34;488.16&#34; y=&#34;104.036&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;source)&lt;/text&gt;&#xA;&lt;path d=&#34;M542.16,108.176L650.16,108.176L650.16,36.1757L542.16,36.1757Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;596.16&#34; y=&#34;72.1757&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;preconv&lt;/text&gt;&#xA;&lt;polygon points=&#34;758.16,72.1757 746.64,76.4957 746.64,67.8557&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M650.16,72.1757L752.4,72.1757&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;704.16&#34; y=&#34;60.4757&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;mom&lt;/text&gt;&#xA;&lt;text x=&#34;704.16&#34; y=&#34;83.8757&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;(sanitized&lt;/text&gt;&#xA;&lt;text x=&#34;704.16&#34; y=&#34;104.036&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;Unicode)&lt;/text&gt;&#xA;&lt;path d=&#34;M758.16,108.176L866.16,108.176L866.16,36.1757L758.16,36.1757Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;812.16&#34; y=&#34;62.0957&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;groff&lt;/text&gt;&#xA;&lt;text x=&#34;812.16&#34; y=&#34;82.2557&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;-mom -Tpdf&lt;/text&gt;&#xA;&lt;polygon points=&#34;974.16,72.1757 962.64,76.4957 962.64,67.8557&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M866.16,72.1757L968.4,72.1757&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;920.16&#34; y=&#34;60.4757&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;PDF&lt;/text&gt;&#xA;&lt;text x=&#34;442.664&#34; y=&#34;12.24&#34; text-anchor=&#34;end&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;mailprint -output.format=mom -face.picon=false&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/mailprint-design/</guid>
      <pubDate>Thu, 22 Jun 2023 22:04:56 +0200</pubDate>
    </item>
    <item>
      <title>Lei, the Local Email Interface</title>
      <link>https://blog.gnoack.org/post/lei</link>
      <description>&lt;h2 id=&#34;intro&#34;&gt;Intro&lt;/h2&gt;&#xA;&lt;p&gt;When I came back to the email account where I first subscribed to LKML,&#xA;it was &lt;em&gt;full&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;p&gt;But if you are anyway using a local email client like &lt;code&gt;mutt&lt;/code&gt;,&#xA;there is a better option than subscribing via email:&#xA;The &lt;code&gt;lei&lt;/code&gt; program lets you import Linux mailing list archives into local maildirs:&lt;/p&gt;&#xA;&lt;div id=&#39;pikchr-0&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:486px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 486.72 165.413&#34;&gt;&#xA;&lt;path d=&#34;M2.16,156.053L131.76,156.053L131.76,84.0529L2.16,84.0529Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;66.96&#34; y=&#34;109.973&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;public-inbox at&lt;/text&gt;&#xA;&lt;text x=&#34;66.96&#34; y=&#34;130.133&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;lore.kernel.org&lt;/text&gt;&#xA;&lt;polygon points=&#34;131.76,120.053 143.28,115.733 143.28,124.373&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M137.52,120.053L203.76,120.053&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;167.76&#34; y=&#34;108.353&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;2. query&lt;/text&gt;&#xA;&lt;path d=&#34;M203.76,156.053L311.76,156.053L311.76,84.0529L203.76,84.0529Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;257.76&#34; y=&#34;120.053&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;lei&lt;/text&gt;&#xA;&lt;polygon points=&#34;398.16,120.053 386.64,124.373 386.64,115.733&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M311.76,120.053L392.4,120.053&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;354.96&#34; y=&#34;108.353&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;3. store&lt;/text&gt;&#xA;&lt;path d=&#34;M398.16,87.6529L398.16,152.453A43.2 10.8 0 0 0 484.56 152.453L484.56,87.6529A43.2 10.8 0 0 0 398.16 87.6529A43.2 10.8 0 0 0 484.56 87.6529&#34;  style=&#34;fill:rgb(255,255,224);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;441.36&#34; y=&#34;118.073&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;local&lt;/text&gt;&#xA;&lt;text x=&#34;441.36&#34; y=&#34;138.233&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;maildir&lt;/text&gt;&#xA;&lt;polygon points=&#34;257.76,84.0529 253.44,72.5329 262.08,72.5329&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M257.76,78.2929L257.76,27.36&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;257.76&#34; y=&#34;55.7065&#34; text-anchor=&#34;start&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt; 1. lei up --all&lt;/text&gt;&#xA;&lt;text x=&#34;257.76&#34; y=&#34;12.24&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;user&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;The server side needs to run the &lt;a href=&#34;https://public-inbox.org&#34;&gt;public-inbox&lt;/a&gt; software.&lt;/p&gt;&#xA;&lt;h2 id=&#34;set-up&#34;&gt;Set up&lt;/h2&gt;&#xA;&lt;p&gt;First, create a directory for your maildirs:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;mkdir -p .Mail/lei&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;Arch Linux installs &lt;code&gt;lei&lt;/code&gt; into an unusual location.  In this case, add this to &lt;code&gt;.bashrc&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;alias lei=/usr/bin/vendor_perl/lei&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;subscriptionsearch-management&#34;&gt;Subscription/Search management&lt;/h2&gt;&#xA;&lt;p&gt;Subscribe to various mailing lists and to the mails that go to yourself (&lt;a href=&#34;https://lore.kernel.org/all/_/text/help/&#34;&gt;Query Language&lt;/a&gt;):&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;# Linux Security Module list&#xA;lei q --only=https://lore.kernel.org/linux-security-module \&#xA;      --output=&amp;quot;${HOME}/.Mail/lei/lsm&amp;quot;                     \&#xA;      &#39;rt:12.months.ago..&#39;&#xA;&#xA;# Linux Man Pages list&#xA;lei q --only=https://lore.kernel.org/linux-man \&#xA;      --output=&amp;quot;${HOME}/.Mail/lei/man&amp;quot;         \&#xA;      &#39;rt:12.months.ago..&#39;&#xA;&#xA;# Mails where I am in To: or Cc:, and the surrounding threads&#xA;lei q --only=https://lore.kernel.org/all \&#xA;      --output=&amp;quot;${HOME}/.Mail/lei/tome&amp;quot;  \&#xA;      --threads                          \&#xA;      &#39;a:myemail@example.com rt:36.months.ago..&#39;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The &lt;code&gt;lei q&lt;/code&gt; command is not issuing a single-shot query,&#xA;but it is storing the query locally as a subscription.&lt;/p&gt;&#xA;&lt;p&gt;These subscriptions are called &amp;ldquo;searches&amp;rdquo; in lei lingo,&#xA;and their results are stored in the given maildir directories.&lt;/p&gt;&#xA;&lt;p&gt;To see all the searches you have, use:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;lei ls-search&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;To remove an existing search, use&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;lei forget-search &amp;quot;${HOME}/.Mail/lei/lsm&amp;quot;  # example&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;regularly-update-subscriptionssearches&#34;&gt;Regularly: Update subscriptions/searches&lt;/h2&gt;&#xA;&lt;p&gt;To update all lei subscriptions and populate new content into the maildirs:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;lei up --all&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Now you are all set, and you can read the mailing lists with the MUA of your choice:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;mutt -f ~/.Mail/lei/lsm&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://people.kernel.org/monsieuricon/lore-lei-part-1-getting-started&#34;&gt;lore+lei: part 1, getting started&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://people.kernel.org/monsieuricon/lore-lei-part-2-now-with-imap&#34;&gt;lore+lei, part 2, now with IMAP&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://lwn.net/Articles/878205/&#34;&gt;LWN: Digging into the community&amp;rsquo;s lore with lei&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Thanks to &lt;a href=&#34;http://derkling.matbug.net/blog:email_setup&#34;&gt;derkling&lt;/a&gt;, who pointed me to &lt;code&gt;lei&lt;/code&gt;!&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/lei/</guid>
      <pubDate>Sun, 11 Jun 2023 20:13:00 +0200</pubDate>
    </item>
    <item>
      <title>The empty Makefile is valid</title>
      <link>https://blog.gnoack.org/post/empty-makefiles</link>
      <description>&lt;p&gt;This is less of a &amp;ldquo;Today I learned&amp;rdquo; article, but more like a Unix&#xA;trick that took me too long to realize.&lt;/p&gt;&#xA;&lt;p&gt;Create a new directory with a hello world C program:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;$ ls&#xA;$ cat &amp;gt; hello.c&#xA;#include &amp;lt;stdio.h&amp;gt;&#xA;&#xA;int main() {&#xA;  puts(&amp;quot;Hello, world!&amp;quot;);&#xA;  return 0;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;💡 The empty or non-existent Makefile is a valid makefile for this C&#xA;program:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;$ make hello                # build &amp;quot;hello&amp;quot;&#xA;cc     hello.c   -o hello&#xA;$ ./hello&#xA;Hello, world!&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/empty-makefiles/</guid>
      <pubDate>Wed, 10 May 2023 19:00:00 +0200</pubDate>
    </item>
    <item>
      <title>Landlock: Best Effort mode</title>
      <link>https://blog.gnoack.org/post/landlock-best-effort</link>
      <description>&lt;ul id=&#34;toc&#34;&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#abi-versions-and-matching-access-rights&#34;&gt;ABI versions and matching access rights&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#the-problem-refer-is-different&#34;&gt;The problem: Refer is different&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#implementation-of-best-effort-fallback-in-c&#34;&gt;Implementation of best-effort fallback in C&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#common-part-abi-version-compatibility-table&#34;&gt;Common part: ABI version compatibility table&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#case-1-the-sandboxed-program-does-not-need-to-reparent-files&#34;&gt;Case 1: the sandboxed program does not need to reparent files&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#case-2-the-sandboxed-program-needs-to-reparent-files&#34;&gt;Case 2: the sandboxed program needs to reparent files&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#case-3-sometimes-this-sometimes-that&#34;&gt;Case 3: Sometimes this, sometimes that&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#conclusion&#34;&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#references&#34;&gt;References&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;One of Landlock&amp;rsquo;s strengths is that you can deploy the same program on&#xA;multiple kernel versions, and make it use the best available&#xA;sandboxing on each.&lt;/p&gt;&#xA;&lt;p&gt;This &amp;ldquo;best effort&amp;rdquo; approach is already implemented for you &lt;a href=&#34;https://pkg.go.dev/github.com/landlock-lsm/go-landlock/landlock#Config.BestEffort&#34;&gt;in the&#xA;Go-Landlock&#xA;library&lt;/a&gt;&#xA;and &lt;a href=&#34;https://landlock.io/rust-landlock/landlock/#compatibility&#34;&gt;in the Rust Landlock&#xA;library&lt;/a&gt;.&#xA;But what if you need to implement it yourself?&lt;/p&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&#xA;This article assumes previous knowledge of Landlock - you can read more about the abstract model in the &lt;a href=&#34;https://docs.google.com/document/d/1SkFpl_Xxyl4E6G2uYIlzL0gY2PFo-Nl8ikblLvnpvlU/edit#&#34;&gt;Landlock File System Access Model&lt;/a&gt; document which I&amp;rsquo;ve written previously, or in the &lt;a href=&#34;https://docs.kernel.org/userspace-api/landlock.html&#34;&gt;official Landlock documentation&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;abi-versions-and-matching-access-rights&#34;&gt;ABI versions and matching access rights&lt;/h2&gt;&#xA;&lt;!-- TODO: Link the man page here when it&#39;s part of the man page --&gt;&#xA;&lt;p&gt;Landlock exposes its feature set in the form of a numeric Landlock ABI&#xA;version.&lt;/p&gt;&#xA;&lt;p&gt;In order to implement the fallback correctly, you need to know which&#xA;file system access rights are supported in which ABI version.&lt;/p&gt;&#xA;&lt;p&gt;The (simplified) compatibility chart as of Linux 6.2 is:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr&gt;&#xA;&lt;th&gt;ABI&lt;/th&gt;&#xA;&lt;th&gt;Linux&lt;/th&gt;&#xA;&lt;th&gt;File system access rights&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;1&lt;/td&gt;&#xA;&lt;td&gt;5.13&lt;/td&gt;&#xA;&lt;td&gt;almost all of the basic file operations (compare &lt;a href=&#34;https://docs.kernel.org/userspace-api/landlock.html#filesystem-flags&#34;&gt;kernel docs&lt;/a&gt;)&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;2&lt;/td&gt;&#xA;&lt;td&gt;5.19&lt;/td&gt;&#xA;&lt;td&gt;+&lt;code&gt;LANDLOCK_ACCESS_FS_REFER&lt;/code&gt; (reparenting files)&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr&gt;&#xA;&lt;td&gt;3&lt;/td&gt;&#xA;&lt;td&gt;6.2&lt;/td&gt;&#xA;&lt;td&gt;+&lt;code&gt;LANDLOCK_ACCESS_FS_TRUNCATE&lt;/code&gt; (truncating files)&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Further below, we will define this support matrix in C.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-problem-refer-is-different&#34;&gt;The problem: Refer is different&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;The backwards compatibility works differently for &amp;ldquo;refer&amp;rdquo; than for&#xA;other access rights&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Skip this section if you are only interested in the C code.&lt;/div&gt;&#xA;&lt;p&gt;Each Landlock ABI version is characterized by three disjoint sets of&#xA;file system operations: The &lt;em&gt;Always Forbidden&lt;/em&gt; operations, the&#xA;&lt;em&gt;Configurable&lt;/em&gt; operations and the &lt;em&gt;Always Permitted&lt;/em&gt; operations.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;em&gt;Configurable&lt;/em&gt; operations are the ones which already have names in&#xA;the &lt;code&gt;landlock.h&lt;/code&gt; header.  With increasing ABI versions, all relevant&#xA;operations should become &amp;ldquo;Configurable&amp;rdquo;, in particular the &amp;ldquo;Always&#xA;permitted&amp;rdquo; operations.&lt;/p&gt;&#xA;&lt;p&gt;In ABI v1, an set of useful file system operations is configurable in&#xA;Landlock, but there is also a longer list of known operations which is&#xA;always permitted (Landlock can not restrict them). Finally, some more&#xA;complicated interactions are always forbidden because they would&#xA;interfere with Landlock&amp;rsquo;s guarantees:&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/ll-permissions.svg&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;When introducing ABI v2, the &amp;ldquo;refer&amp;rdquo; operation, which was previously&#xA;forbidden, became configurable:&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/ll-permissions-v2.svg&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;With ABI v2, it is now possible to reparent (link(2) or rename(2))&#xA;files between different directories, as long as the involved files and&#xA;directories meet certain constraints about their access rights.&lt;/p&gt;&#xA;&lt;p&gt;The behavior of &amp;ldquo;refer&amp;rdquo; is different to &amp;ldquo;truncate&amp;rdquo; and other future access rights, which fall back to &lt;em&gt;Always Permitted&lt;/em&gt; in earlier ABI versions, and so &amp;ldquo;refer&amp;rdquo; needs a different fallback logic.&lt;/p&gt;&#xA;&lt;blockquote class=&#34;rule&#34;&gt;&lt;p&gt;If a program needs to do a &amp;ldquo;refer&amp;rdquo; (reparent files between directories),&#xA;using ABI v1 is not an option.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;!-- todo: mention man page --&gt;&#xA;&lt;p&gt;The canonical documentation for this interaction is the &lt;a href=&#34;https://docs.kernel.org/userspace-api/landlock.html#filesystem-flags&#34;&gt;kernel&#xA;documentation&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;implementation-of-best-effort-fallback-in-c&#34;&gt;Implementation of best-effort fallback in C&lt;/h2&gt;&#xA;&lt;p&gt;To implement the fallback logic, we will (a) define the compatibility&#xA;table, and then, (b) depending on whether your program needs to do&#xA;file reparenting, implement a slightly different fallback logic based&#xA;on that compatibility table.&lt;/p&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;This question decides which approach to use.&lt;/div&gt;&#xA;&lt;p&gt;The question to ask is:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Does your program need to do any file reparenting (&amp;ldquo;refer&amp;rdquo;)?&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;File reparenting means: Creating hard links or moving files or&#xA;directories, between &lt;em&gt;different&lt;/em&gt;(!) directories (compare &lt;a href=&#34;https://docs.kernel.org/userspace-api/landlock.html#filesystem-flags&#34;&gt;the kernel&#xA;documentation&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;h3 id=&#34;common-part-abi-version-compatibility-table&#34;&gt;Common part: ABI version compatibility table&lt;/h3&gt;&#xA;&lt;p&gt;We define a small C array to hold the file system access rights which&#xA;are supported by the different Landlock ABI versions.  We store one&#xA;&lt;code&gt;__u64&lt;/code&gt; bitmask for each ABI version.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;__u64 landlock_fs_access_rights[] = {&#xA;  (1ULL &amp;lt;&amp;lt; 13) - 1,  /* ABI v1                 */&#xA;  (1ULL &amp;lt;&amp;lt; 14) - 1,  /* ABI v2: add &amp;quot;refer&amp;quot;    */&#xA;  (1ULL &amp;lt;&amp;lt; 15) - 1,  /* ABI v3: add &amp;quot;truncate&amp;quot; */&#xA;};&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I&amp;rsquo;m keeping it short here for brevity, but you can also use the constants from &lt;a href=&#34;https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/landlock.h?h=v6.2&#34;&gt;linux/landlock.h&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;case-1-the-sandboxed-program-does-not-need-to-reparent-files&#34;&gt;Case 1: the sandboxed program does &lt;em&gt;not&lt;/em&gt; need to reparent files&lt;/h3&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Use this approach, if you don&#39;t use link(2) and rename(2) across directory boundaries.&lt;/div&gt;&#xA;&lt;p&gt;If the sandboxed program does &lt;em&gt;not&lt;/em&gt; need to reparent files, the&#xA;best-effort logic is to simply remove the unsupported rights from &lt;code&gt;ruleset_attr.handled_access_fs&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This code needs to be inserted &lt;em&gt;after&lt;/em&gt; you have filled a &lt;code&gt;struct landlock_ruleset_attr&lt;/code&gt; variable, but before you create the ruleset&#xA;from it:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;int abi = landlock_create_ruleset(NULL, 0,&#xA;                                  LANDLOCK_CREATE_RULESET_VERSION);&#xA;if (abi &amp;lt;= 0) {&#xA;  /*&#xA;   * This kernel does not let us use Landlock.&#xA;   * Best effort: Give up, and do not restrict the process.&#xA;   */&#xA;  return 0;&#xA;}&#xA;if (abi &amp;gt; ARRAY_SIZE(landlock_fs_access_rights)) {&#xA;  /* Kernel from the future: Treat as highest known ABI version. */&#xA;  abi = ARRAY_SIZE(landlock_fs_access_rights);&#xA;}&#xA;ruleset_attr.handled_access_fs &amp;amp;= landlock_fs_access_rights[abi-1];&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Don&#39;t forget this!&lt;/div&gt;&#xA;&lt;p&gt;Additionally, before you add a &amp;ldquo;path beneath&amp;rdquo; Landlock rule to the&#xA;ruleset, make sure that the requested access rights fit into the&#xA;previously restricted &lt;code&gt;handled_access_fs&lt;/code&gt; rights:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;path_beneath.allowed_access &amp;amp;= ruleset_attr.handled_access_fs;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h3 id=&#34;case-2-the-sandboxed-program-needs-to-reparent-files&#34;&gt;Case 2: the sandboxed program needs to reparent files&lt;/h3&gt;&#xA;&lt;p&gt;If the sandboxed program &lt;em&gt;does&lt;/em&gt; need to reparent files, the&#xA;best-effort logic works the same as above, &lt;em&gt;but it has a special case&#xA;for ABI version 1&lt;/em&gt;.  In this ABI version, file reparenting does not&#xA;work under Landlock at all, and so we have to give up on it.&lt;/p&gt;&#xA;&lt;p&gt;This code needs to be inserted &lt;em&gt;after&lt;/em&gt; you have filled a &lt;code&gt;struct landlock_ruleset_attr&lt;/code&gt; variable, but before you create the ruleset&#xA;from it:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;int abi = landlock_create_ruleset(NULL, 0,&#xA;                                  LANDLOCK_CREATE_RULESET_VERSION);&#xA;switch (abi) {&#xA;  case -1:&#xA;  case 0:  /* should not happen */&#xA;    /*&#xA;     * This kernel does not let us use Landlock.&#xA;     * Best effort: Give up, and do not restrict the process.&#xA;     */&#xA;    return 0;&#xA;  case 1:&#xA;    /*&#xA;     * This kernel only supports Landlock ABI v1.&#xA;     * We need the &amp;quot;refer&amp;quot; right, but it&#39;s not supported in ABI v1.&#xA;     * Best effort: Give up, and do not restrict the process.&#xA;     */&#xA;    return 0;&#xA;  default:&#xA;    if (abi &amp;gt; ARRAY_SIZE(landlock_fs_access_rights)) {&#xA;      /* &#xA;       * A kernel with Landlock suppport from the future!&#xA;       * Treat it as the highest known ABI version.&#xA;       */&#xA;      abi = ARRAY_SIZE(landlock_fs_access_rights);&#xA;    }&#xA;    ruleset_attr.handled_access_fs &amp;amp;= landlock_fs_access_rights[abi-1];&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Additionally, before you add a &amp;ldquo;path beneath&amp;rdquo; Landlock rule to the&#xA;ruleset, make sure that the requested access rights fit into the&#xA;previously restricted &lt;code&gt;handled_access_fs&lt;/code&gt; rights:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;path_beneath.allowed_access &amp;amp;= ruleset_attr.handled_access_fs;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h3 id=&#34;case-3-sometimes-this-sometimes-that&#34;&gt;Case 3: Sometimes this, sometimes that&lt;/h3&gt;&#xA;&lt;p&gt;This is the complicated case which we also implemented in the Go and&#xA;Rust Landlock libraries.  These libraries figure out at runtime which&#xA;of the two cases we are in, and then use the adequate approach as&#xA;above.&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;&#xA;&lt;p&gt;In the long run, when Landlock has stabilized more, and as older&#xA;kernels become less relevant, this complication will hopefully go&#xA;away.  Until then, I&amp;rsquo;m hopeful that most users can simply use the&#xA;simple approach in Case 1 above, because their programs do not need&#xA;the &amp;ldquo;refer&amp;rdquo; right.&lt;/p&gt;&#xA;&lt;h2 id=&#34;references&#34;&gt;References&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I&amp;rsquo;ve previously written about this more in the abstract at: &lt;a href=&#34;https://docs.google.com/document/d/1SkFpl_Xxyl4E6G2uYIlzL0gY2PFo-Nl8ikblLvnpvlU/edit#&#34;&gt;Landlock File System Access&#xA;Model&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://landlock.io/&#34;&gt;https://landlock.io/&lt;/a&gt; - Official Landlock page&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.kernel.org/userspace-api/landlock.html&#34;&gt;Landlock kernel documentation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Thank you Mickaël, for pointing out some issues in this article!&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/landlock-best-effort/</guid>
      <pubDate>Sun, 05 Mar 2023 10:34:09 +0100</pubDate>
    </item>
    <item>
      <title>Landlock truncation support in Linux 6.2</title>
      <link>https://blog.gnoack.org/post/landlock-truncate</link>
      <description>&lt;p&gt;Linus Torvalds released Linux 6.2 yesterday, and this kernel release&#xA;supports restricting the &lt;code&gt;truncate(2)&lt;/code&gt; and &lt;code&gt;ftruncate(2)&lt;/code&gt; operations&#xA;with Landlock. (The &lt;a href=&#34;https://lore.kernel.org/all/20221018182216.301684-1-gnoack3000@gmail.com/&#34;&gt;kernel patch&#xA;set&lt;/a&gt;&#xA;has more information and discussion.)&lt;/p&gt;&#xA;&lt;p&gt;You can try this out today with the&#xA;&lt;a href=&#34;https://github.com/landlock-lsm/go-landlock&#34;&gt;go-landlock&lt;/a&gt; library,&#xA;which already supports this feature. To forbid file truncation when&#xA;using &lt;code&gt;go-landlock&lt;/code&gt;, update your &lt;code&gt;RestrictPaths()&lt;/code&gt; invocation to use&#xA;Landlock version 3 as follows:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;err := landlock.V3.BestEffort().RestrictPaths(&#xA;    landlock.RODirs(&amp;quot;/usr&amp;quot;, &amp;quot;/bin&amp;quot;),&#xA;    landlock.RWDirs(&amp;quot;/tmp&amp;quot;),&#xA;)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Most existing users will only need to exchange &lt;code&gt;V2&lt;/code&gt; for &lt;code&gt;V3&lt;/code&gt;. When&#xA;using &lt;code&gt;landlock.V3&lt;/code&gt; this way, file truncation is forbidden by default.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;RWFiles()&lt;/code&gt; and &lt;code&gt;RWDirs()&lt;/code&gt; helpers grant the truncation right when&#xA;used on a file or directory. (It comes hand in hand with the right to&#xA;open files for writing.)&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/landlock-truncate/</guid>
      <pubDate>Mon, 20 Feb 2023 21:20:00 +0100</pubDate>
    </item>
    <item>
      <title>Why use Go-Landlock for sandboxing?</title>
      <link>https://blog.gnoack.org/post/go-landlock-talk</link>
      <description>&lt;ul id=&#34;toc&#34;&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#motivation&#34;&gt;Motivation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#philosophy&#34;&gt;Philosophy&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#the-landlock-approach&#34;&gt;The Landlock approach&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#1-make-it-really-easy-to-use&#34;&gt;1. Make it really easy to use&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#2-make-sandboxing-enablement-part-of-program-initialization&#34;&gt;2. Make sandboxing enablement part of program initialization&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#other-operating-systems&#34;&gt;Other operating systems&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#other-linux-sandboxing-technologies&#34;&gt;Other Linux sandboxing technologies&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#how-to-use-go-landlock&#34;&gt;How to use Go-Landlock&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#step-1-make-sure-your-kernel-supports-landlock&#34;&gt;Step 1: Make sure your kernel supports Landlock&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#step-2-state-what-file-accesses-you-are-going-to-do&#34;&gt;Step 2: State what file accesses you are going to do!&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#examples&#34;&gt;Examples&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#image-converter&#34;&gt;Image converter&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#web-server&#34;&gt;Web Server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#the-go-landlock-example-tool&#34;&gt;The Go-Landlock example tool&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#current-landlock-limitations&#34;&gt;Current Landlock limitations&lt;/a&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#what-file-system-operations-are-restrictable&#34;&gt;What file system operations are restrictable?&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#summary&#34;&gt;Summary&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;a href=&#34;#further-links&#34;&gt;Further Links&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;This is the article version of a talk I presented a the Zurich&#xA;Gophers Meetup on 2022-10-25, with some less relevant parts&#xA;shortened.&lt;/p&gt;&#xA;&lt;p&gt;The slides are also available at&#xA;&lt;a href=&#34;https://blog.gnoack.org/talks/go-landlock&#34;&gt;https://blog.gnoack.org/talks/go-landlock&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;style&gt;&#xA;img { border-radius: 1em; border: 1px solid #0001; }&#xA;&lt;/style&gt;&#xA;&lt;h2 id=&#34;motivation&#34;&gt;Motivation&lt;/h2&gt;&#xA;&lt;img src=&#34;https://blog.gnoack.org/images/go-landlock-logo.svg&#34; style=&#34;float: right; max-width: 20%; border: none;&#34;/&gt;&#xA;&lt;p&gt;I started to get interested in computer security about 20 years ago.&#xA;In the late 90&amp;rsquo;s and early 2000&amp;rsquo;s, buffer overflow exploits were all&#xA;the rage and started to be more widely understood and researched.&lt;/p&gt;&#xA;&lt;p&gt;This image depicts a high level overview over a common pattern of a&#xA;technical attack (&amp;ldquo;exploit&amp;rdquo;) on a computer program:&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/talks/go-landlock/Go-Landlock-1.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;The attack channel can take many forms, such as&#xA;over the network, by invoking a privileged executable file, or by&#xA;distributing crafted input files.&lt;/div&gt;&#xA;The attacker talks to the attacked process through the same channels&#xA;like any other user would do. But unlike another user&#39;s input, the&#xA;attacker&#39;s input is maliciously crafted to trick the attacked process&#xA;into misinterpreting or wrongly validating it, and thereby doing&#xA;things on behalf of the attacker that it was not originally designed&#xA;to do.&#xA;&lt;p&gt;This can range from exposing process-local memory (e.g.&#xA;&lt;a href=&#34;https://en.wikipedia.org/wiki/Heartbleed&#34;&gt;Heartbleed&lt;/a&gt;), to exposing&#xA;the contents of files that were not intended to be exposed (e.g.&#xA;&lt;a href=&#34;https://en.wikipedia.org/wiki/Directory_traversal_attack&#34;&gt;directory traversal&#xA;attacks&lt;/a&gt;),&#xA;to even giving the attacker full control over the attacked process&#xA;(e.g. a &lt;a href=&#34;https://en.wikipedia.org/wiki/Buffer_overflow#Exploitation&#34;&gt;classic buffer overflow&#xA;exploit&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;p&gt;Now all of this would be less critical, if the attacked process only&#xA;had access to the resources that it needs for execution, but as it&#xA;turns out, in the normal UNIX model, access rights are usually&#xA;determined by the user that a program runs as, and that often means&#xA;that these programs have access to significantly more things than they&#xA;need. (&lt;a href=&#34;https://en.wikipedia.org/wiki/Ambient_authority&#34;&gt;Ambient&#xA;Authority&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;p&gt;For instance, programs that you run on your desktop are going to have&#xA;access to your bank documents, your (hopefully encrypted) SSH and PGP&#xA;keys, your cookies, your git repositories, etc.&lt;/p&gt;&#xA;&lt;p&gt;&amp;ndash; so: &lt;strong&gt;Let&amp;rsquo;s limit this ambient access!&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;philosophy&#34;&gt;Philosophy&lt;/h2&gt;&#xA;&lt;p&gt;I can&amp;rsquo;t speak for Mickaël Salaün&amp;rsquo;s vision for sandboxing, but this is&#xA;mine, and it aligns very well with Landlock&amp;rsquo;s approach.&lt;/p&gt;&#xA;&lt;p&gt;First of all, I hold the belief that:&lt;/p&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;&lt;strong&gt;Software authors are generally well-meaning&lt;/strong&gt;. They want their&#xA;software to work, and to be secure. If provided with the right tools&#xA;for securing applications, they will use them.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;However, if we take a look at the adoption of unprivileged sandboxing&#xA;on Linux (namely, seccomp-bpf), it shows that there are only a handful&#xA;of programs making use of it &amp;ndash; but what is the reason for that?&lt;/p&gt;&#xA;&lt;p&gt;Which leads me to this hypothesis:&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/talks/go-landlock/Go-Landlock-4.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;&lt;strong&gt;It is too difficult&lt;/strong&gt; for software authors to confine their software&#xA;to have &lt;em&gt;just&lt;/em&gt; the access that the software needs, even though these&#xA;software authors are in the best position to reason about the required&#xA;scope of access.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h3 id=&#34;the-landlock-approach&#34;&gt;The Landlock approach&lt;/h3&gt;&#xA;&lt;p&gt;There are two main points that I&amp;rsquo;d like to push for in Go-Landlock,&#xA;which I think make it more usable for sandboxing than other&#xA;approaches:&lt;/p&gt;&#xA;&lt;h4 id=&#34;1-make-it-really-easy-to-use&#34;&gt;1. Make it really easy to use&lt;/h4&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/talks/go-landlock/Go-Landlock-5.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;The existing confinement approaches on Linux tend to be too difficult&#xA;to set up or maintain, which means that they don&amp;rsquo;t get used as much as&#xA;they should.&lt;/p&gt;&#xA;&lt;h4 id=&#34;2-make-sandboxing-enablement-part-of-program-initialization&#34;&gt;2. Make sandboxing enablement part of program initialization&lt;/h4&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/talks/go-landlock/Go-Landlock-6.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;This is the other main idea &amp;ndash; it should be up to each process to&#xA;enable its own sandbox.&lt;/p&gt;&#xA;&lt;p&gt;The rough approach is:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The program &lt;strong&gt;initializes itself&lt;/strong&gt; &amp;ndash; parses flags, opens the necessary files, named UNIX sockets, etc.&lt;/li&gt;&#xA;&lt;li&gt;The program &lt;strong&gt;restricts its own access&lt;/strong&gt; (using Landlock)&lt;/li&gt;&#xA;&lt;li&gt;The program &lt;strong&gt;starts processing untrusted input&lt;/strong&gt; from potentially malicious sources&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;Note: &lt;strong&gt;If programs sandbox themselves, they can drop more permissions&lt;/strong&gt;,&#xA;because they can also drop the permissions that were required for&#xA;their initialization phase.&lt;/p&gt;&#xA;&lt;p&gt;UNIX enforces permissions when &lt;em&gt;opening&lt;/em&gt; files, so an already opened&#xA;file can continue to get used by a process, even when it does not have&#xA;the permissions to open the file again.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h3 id=&#34;other-operating-systems&#34;&gt;Other operating systems&lt;/h3&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/talks/go-landlock/Go-Landlock-7.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;These ideas are not new - OpenBSD has demonstrated the feasability of&#xA;this approach with &lt;code&gt;pledge()&lt;/code&gt; and &lt;code&gt;unveil()&lt;/code&gt;, which is now used in&#xA;&lt;a href=&#34;https://github.com/openbsd/src/search?l=C&amp;amp;q=unveil&#34;&gt;many of OpenBSD&amp;rsquo;s userland&#xA;programs&lt;/a&gt;. Various&#xA;slide decks on the topic can be found at&#xA;&lt;a href=&#34;https://www.openbsd.org/events.html&#34;&gt;https://www.openbsd.org/events.html&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://wiki.freebsd.org/Capsicum&#34;&gt;Capsicum on FreeBSD&lt;/a&gt; is another&#xA;unprivileged sandboxing mechanism, which is used in Chromium and other&#xA;programs (see its homepage). Capsicum is a flexible capability system,&#xA;but it also has a larger API surface.&lt;/p&gt;&#xA;&lt;h3 id=&#34;other-linux-sandboxing-technologies&#34;&gt;Other Linux sandboxing technologies&lt;/h3&gt;&#xA;&lt;p&gt;A deeper discussion of seccomp-bpf and the various Linux Security&#xA;Modules would be beyond the scope of this article.&lt;/p&gt;&#xA;&lt;p&gt;For a discussion of the other unprivileged sandboxing mechanism, see&#xA;&lt;a href=&#34;https://blog.gnoack.org/post/pledge-on-linux/&#34;&gt;my previous article about&#xA;it&lt;/a&gt; on this blog.&lt;/p&gt;&#xA;&lt;p&gt;The other Linux sandboxing mechanisms are either only available to&#xA;privileged users, or they are still very difficult to set up.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-to-use-go-landlock&#34;&gt;How to use Go-Landlock&lt;/h2&gt;&#xA;&lt;p&gt;This diagram shows the overall approach for program initialization&#xA;when using Landlock (it&amp;rsquo;s a more detailed view of the program&#xA;initialization overview diagram from above):&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/talks/go-landlock/Go-Landlock-10.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;The important parts here are:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Primarily, &lt;strong&gt;Landlock is a Linux kernel feature&lt;/strong&gt; &amp;ndash; the access&#xA;policies enforced through Landlock apply to both the enforcing&#xA;program, as well as all newly forked subprocesses, independent of&#xA;the libraries being used to do these accesses.&lt;/li&gt;&#xA;&lt;li&gt;The &lt;strong&gt;Go-Landlock library is only required to configure and enforce&#xA;the Landlock policy&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The steps required to enforce Landlock are:&lt;/p&gt;&#xA;&lt;h3 id=&#34;step-1-make-sure-your-kernel-supports-landlock&#34;&gt;Step 1: Make sure your kernel supports Landlock&lt;/h3&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/talks/go-landlock/Go-Landlock-11.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;&lt;strong&gt;For development&lt;/strong&gt;, it&amp;rsquo;s recommended to double check that Landlock is&#xA;already working, so that you can try out your policies. Landlock is&#xA;already enabled on many major distributions today.&lt;/p&gt;&#xA;&lt;p&gt;On most systems, you can check whether Landlock is working using &lt;code&gt;cat /sys/kernel/security/lsm&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;On systems that do not have it yet:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Make sure Landlock is compiled into the kernel.&lt;/li&gt;&#xA;&lt;li&gt;Set the &lt;code&gt;lsm=landlock&lt;/code&gt; boot parameter (or configure it in&#xA;&lt;code&gt;CONFIG_LSM&lt;/code&gt; at build time).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;For deployment&lt;/strong&gt;, you can pick whether your program should insist on&#xA;running on a Landlock-enabled kernel, or whether it should fall back&#xA;to using weaker (or no) Landlock policies on older systems:&lt;/p&gt;&#xA;&lt;h3 id=&#34;step-2-state-what-file-accesses-you-are-going-to-do&#34;&gt;Step 2: State what file accesses you are going to do!&lt;/h3&gt;&#xA;&lt;p&gt;This is the only function invocation your program needs in order to&#xA;confine itself in a Landlock policy:&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/talks/go-landlock/Go-Landlock-12.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;landlock.V2&lt;/code&gt;&lt;/strong&gt; determines the Landlock ABI version. A higher ABI&#xA;version means that more operations can be restricted.&#xA;(Alternatively, you can also spell out the exact operations that you&#xA;want to restrict.)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;.BestEffort()&lt;/code&gt;&lt;/strong&gt; is an optional call &amp;ndash; it configures the library to&#xA;gracefully degrade to weaker Landlock policies on systems that do&#xA;not have the requested Landlock features.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;.RestrictPaths()&lt;/code&gt;&lt;/strong&gt; is the final call which enforces the rules.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;The arguments to &lt;code&gt;.RestrictPaths()&lt;/code&gt;&lt;/strong&gt; are file system hierarchies&#xA;that the process intends to still access going forward. Access to&#xA;all paths other than the listed ones will be forbidden, as much as&#xA;the Landlock ABI permits. (Landlock does not currently restrict&#xA;&lt;em&gt;all&lt;/em&gt; possible file system operations, but that&amp;rsquo;s eventually the&#xA;goal. A more detailed look is in the appendix below.)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;landlock.RODirs&lt;/code&gt; and &lt;code&gt;landlock.RWDirs&lt;/code&gt;&lt;/strong&gt; are shortcuts to&#xA;identify &amp;ldquo;general read-only&amp;rdquo; operations and &amp;ldquo;general read-write&#xA;operations&amp;rdquo;. This can also be configured in more detail if needed.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&amp;hellip;and that&amp;rsquo;s it. After this invocation, your program will be unable&#xA;to work with files other than the ones specified or the ones that have&#xA;already been opened before.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://pkg.go.dev/github.com/landlock-lsm/go-landlock/landlock&#34;&gt;Link to the full documentation&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;examples&#34;&gt;Examples&lt;/h2&gt;&#xA;&lt;p&gt;This section will list a few practical examples.&lt;/p&gt;&#xA;&lt;h3 id=&#34;image-converter&#34;&gt;Image converter&lt;/h3&gt;&#xA;&lt;p&gt;This is a basic image conversion tool in the spirit of ImageMagick&amp;rsquo;s&#xA;&amp;ldquo;convert&amp;rdquo;. Multimedia processing libraries are often optimized for&#xA;performance and can be particularly prone to programming bugs, and we&#xA;want to protect against attackers providing malicious image files as&#xA;input.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/talks/go-landlock/Go-Landlock-13.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;In this example, the Landlock invocation happens right at the start&#xA;and is &lt;code&gt;landlock.V2.BestEffort().RestrictPaths()&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;We simply restrict to &lt;strong&gt;no file system access at all&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;li&gt;But we fall back to enforcing weaker Landlock policies or none, if&#xA;it&amp;rsquo;s not supported by the system where the program is running.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/landlock-lsm/go-landlock/blob/main/examples/convert/main.go&#34;&gt;Link to the full example&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;web-server&#34;&gt;Web Server&lt;/h3&gt;&#xA;&lt;p&gt;This simple wiki software only needs access to the directory where the&#xA;wiki pages are stored.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/talks/go-landlock/Go-Landlock-14.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;The Go-Landlock invocation is:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;err = landlock.V2.BestEffort().RestrictPaths(&#xA;    landlock.RWDirs(*storeDir),&#xA;)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This gives the process the right to serve and edit the wiki pages, but&#xA;removes the rights for all other file paths (within the limits of what&#xA;is possible on the current system).&lt;/p&gt;&#xA;&lt;p&gt;(Small side note: The &lt;code&gt;net.Listen()&lt;/code&gt; call happens before Landlock&#xA;enablement &amp;ndash; I am using this with a named UNIX domain socket.)&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/gnoack/ukuleleweb/blob/main/cmd/ukuleleweb/main.go&#34;&gt;Link to the full example&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-go-landlock-example-tool&#34;&gt;The Go-Landlock example tool&lt;/h3&gt;&#xA;&lt;p&gt;The Go-landlock library comes with a simple example tool, which lets&#xA;you play with Landlock from bash, similar to the one in the&#xA;&lt;code&gt;samples/landlock&lt;/code&gt; subdirectory in the kernel source, but written&#xA;using the Go library:&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/talks/go-landlock/Go-Landlock-15.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;The command to install the &lt;code&gt;landlock-restrict&lt;/code&gt; tool is:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;go install github.com/landlock-lsm/go-landlock/cmd/landlock-restrict@latest&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The above shell transcript starts &lt;code&gt;bash&lt;/code&gt; under a Landlock policy where&#xA;it only has access to these files:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Read access to &lt;code&gt;/usr&lt;/code&gt; and &lt;code&gt;/lib&lt;/code&gt;&lt;/strong&gt; is needed for shared libraries&#xA;and the &lt;code&gt;bash&lt;/code&gt; executable itself.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Read access to &lt;code&gt;/etc&lt;/code&gt;&lt;/strong&gt; is required for some common configuration&#xA;files.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Write access to &lt;code&gt;/dev&lt;/code&gt;&lt;/strong&gt; is needed for some specific files like&#xA;&lt;code&gt;/dev/null&lt;/code&gt;, &lt;code&gt;/dev/stdout&lt;/code&gt; and others. (Could be made more specific.)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Write access to $HOME&lt;/strong&gt;: We rewire the &lt;code&gt;$HOME&lt;/code&gt; environment&#xA;variable to a temporary directory for our subprocess and set&#xA;&lt;code&gt;$TMPDIR&lt;/code&gt; to a directory within it, so we can just grant write&#xA;access to &lt;code&gt;$HOME&lt;/code&gt;. (This trick makes it possible to use a coarser&#xA;policy, but it lets the sandboxes process execute in a slightly less&#xA;usual environment.)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/landlock-lsm/go-landlock/blob/main/cmd/landlock-restrict/main.go&#34;&gt;Link to the &lt;code&gt;landlock-restrict&lt;/code&gt; example tool&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;current-landlock-limitations&#34;&gt;Current Landlock limitations&lt;/h2&gt;&#xA;&lt;p&gt;Some things that Landlocked processes can never do are:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;They can not &lt;strong&gt;manipulate the file system topology&lt;/strong&gt; (e.g. &lt;code&gt;mount&lt;/code&gt;, &lt;code&gt;pivot_root&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;Landlocked processes have the &lt;strong&gt;&lt;code&gt;NO_NEW_PRIVS&lt;/code&gt; flag&lt;/strong&gt; &amp;ndash; you can not execute suid root binaries&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Restricted use of &lt;code&gt;ptrace()&lt;/code&gt;&lt;/strong&gt; (debugging other processes)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Also,&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Landlock is &lt;em&gt;in development&lt;/em&gt;&lt;/li&gt;&#xA;&lt;li&gt;Some file operations are not restrictable yet&lt;/li&gt;&#xA;&lt;li&gt;But it&amp;rsquo;s already limiting the most common ones and it&amp;rsquo;s already usable&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;what-file-system-operations-are-restrictable&#34;&gt;What file system operations are restrictable?&lt;/h3&gt;&#xA;&lt;p&gt;Landlock gains features and makes it possible to restrict more file&#xA;system operations. To make this observable, Landlock&amp;rsquo;s ABI is&#xA;versioned. As of November 2022, the current ABI is V2.&lt;/p&gt;&#xA;&lt;p&gt;The list of restrictable file system operations is &lt;a href=&#34;https://docs.kernel.org/userspace-api/landlock.html#filesystem-flags&#34;&gt;documented in the&#xA;Landlock&#xA;documentation&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;blockquote class=&#34;warning&#34;&gt;&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: Not all file system operations can be restricted&#xA;yet. Currently, notable exceptions are file truncation (as a form of&#xA;file modification), and various ways to observe presence and metadata&#xA;of files (but not reading their contents).&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;In future Landlock ABI versions, new operations will start to be&#xA;configurable. Currently ongoing patch sets (as of November 2022) are:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;File truncation (currently scheduled for inclusion in kernel 6.2)&lt;/li&gt;&#xA;&lt;li&gt;Chmod and Chown (patch set in review)&lt;/li&gt;&#xA;&lt;li&gt;Networking support (patch set in review)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;In short, please try it out! The invocation is as simple as:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;err := landlock.V2.BestEffort().RestrictPaths(&#xA;    landlock.RODirs(&amp;quot;/usr&amp;quot;, &amp;quot;/bin&amp;quot;),&#xA;    landlock.RWDirs(&amp;quot;/tmp&amp;quot;),&#xA;)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I would love to hear your feedback!&lt;/p&gt;&#xA;&lt;h2 id=&#34;further-links&#34;&gt;Further Links&lt;/h2&gt;&#xA;&lt;p&gt;For further reference, here are some additional links:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Go-Landlock:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;On Github: &lt;a href=&#34;https://github.com/landlock-lsm/go-landlock&#34;&gt;https://github.com/landlock-lsm/go-landlock&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Docs: &lt;a href=&#34;https://pkg.go.dev/github.com/landlock-lsm/go-landlock/landlock&#34;&gt;https://pkg.go.dev/github.com/landlock-lsm/go-landlock/landlock&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Landlock kernel project:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Page: &lt;a href=&#34;https://landlock.io/&#34;&gt;https://landlock.io/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Kernel doc: &lt;a href=&#34;https://docs.kernel.org/userspace-api/landlock.html&#34;&gt;https://docs.kernel.org/userspace-api/landlock.html&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Mailing list: &lt;a href=&#34;https://lore.kernel.org/landlock&#34;&gt;https://lore.kernel.org/landlock&lt;/a&gt; (To subscribe, mail&#xA;&lt;a href=&#34;mailto:landlock+subscribe@lists.linux.dev&#34;&gt;landlock+subscribe@lists.linux.dev&lt;/a&gt;)&lt;/li&gt;&#xA;&lt;li&gt;Some more documentation:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.google.com/document/d/1SkFpl_Xxyl4E6G2uYIlzL0gY2PFo-Nl8ikblLvnpvlU/edit&#34;&gt;Landlock File System Access&#xA;Model&lt;/a&gt;&#xA;(discussing the kernel API and configurable file system operations&#xA;in a more mathematical way).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/go-landlock-talk/</guid>
      <pubDate>Mon, 14 Nov 2022 20:00:00 +0100</pubDate>
    </item>
    <item>
      <title>The feasibility of pledge() on Linux</title>
      <link>https://blog.gnoack.org/post/pledge-on-linux</link>
      <description>&lt;p&gt;So, there was a &lt;a href=&#34;https://justine.lol/pledge/&#34;&gt;post by Justine Tunney&lt;/a&gt;&#xA;about her port of &lt;a href=&#34;https://man.openbsd.org/pledge.2&#34;&gt;OpenBSD&amp;rsquo;s&#xA;&lt;code&gt;pledge()&lt;/code&gt;&lt;/a&gt; to her own libc, the&#xA;&lt;a href=&#34;https://justine.lol/cosmopolitan/&#34;&gt;Cosmopolitan libc&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;She is also calling out that previous attempts at this were flawed:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;There&amp;rsquo;s been a few devs in the past who&amp;rsquo;ve tried this. I&amp;rsquo;m not going to name names, because most of these projects were never completed. [&amp;hellip;] The projects that got further along also had oversights like allowing the changing of setuid/setgid/sticky bits. So none of the current alternatives should be used.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;My own &lt;a href=&#34;https://github.com/gnoack/seccomp-scopes&#34;&gt;seccomp-scopes&lt;/a&gt;&#xA;project which I worked on from 2016 onwards is one of these attempts,&#xA;so I feel I should explain the reasons &lt;em&gt;why&lt;/em&gt; I stopped pursuing this&#xA;approach of unprivileged sandboxing, and what I think needs to get&#xA;done to do it right.&lt;/p&gt;&#xA;&lt;p&gt;At the high level, the main problem is that seccomp-bpf does its&#xA;filtering at the level of system calls and &lt;strong&gt;software libraries&#xA;generally do not give guarantees about which system calls they are&#xA;using under the hood&lt;/strong&gt;. This does not even hold for &lt;code&gt;libc&lt;/code&gt;&#xA;implementations.&lt;/p&gt;&#xA;&lt;h2 id=&#34;you-cant-predict-the-syscalls-a-program-will-do&#34;&gt;You can&amp;rsquo;t predict the syscalls a program will do&lt;/h2&gt;&#xA;&lt;p&gt;Here are some ways in which glibc makes it hard to predict which&#xA;system calls it will do:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;glibc replaces existing uses of system calls with newer variants. A&#xA;call to the &lt;code&gt;open()&lt;/code&gt; libc function used the &lt;code&gt;openat(2)&lt;/code&gt; syscall&#xA;under the hood, and that is just one of many examples. This changes&#xA;between glibc versions.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;glibc initializes parts of the library on demand when they are first&#xA;used, and that may involve system calls that should better be&#xA;forbidden. So this initialization needs to ideally be done before&#xA;enforcing seccomp.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;glibc makes use of shared libraries for commonly used functionality&#xA;(&lt;a href=&#34;https://man7.org/linux/man-pages/man5/nsswitch.conf.5.html&#34;&gt;&lt;code&gt;nsswitch.conf&lt;/code&gt;&lt;/a&gt;).&#xA;Administrators can flexibly install additional ways of doing name&#xA;lookups, but any attempt at reasoning about this will need to&#xA;involve these shared libraries as well.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;For example: If a program calls &lt;code&gt;gethostbyname()&lt;/code&gt; for the first time, the following things happen:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;It looks up &lt;code&gt;/etc/nsswitch.conf&lt;/code&gt; to find the shared libraries that implement hostname lookup (system calls: various file accesses)&lt;/li&gt;&#xA;&lt;li&gt;It loads these shared libraries (system calls: various file accesses, various address space manipulation syscalls)&lt;/li&gt;&#xA;&lt;li&gt;It calls these shared libraries to do name lookup (system calls: you can&amp;rsquo;t tell anymore)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;There is additionally the problem that at the system call layer, DNS&#xA;lookups are indistinguishable from other UDP socket operations, so&#xA;allow-listing DNS will probably allow other UDP traffic as well.&lt;/p&gt;&#xA;&lt;p&gt;So, to summarize: Attempting to implement a &lt;code&gt;pledge()&lt;/code&gt; like call with&#xA;seccomp-bpf and independent of a specific libc is an inherently&#xA;brittle approach, which &lt;strong&gt;involves keeping up-to-date lists of system&#xA;calls on different kernel versions and architectures and their use by&#xA;different libcs&lt;/strong&gt;. The complexity and feature-richness of glibc&#xA;(particularly libnss) makes this particularly difficult. Any&#xA;libc-independent &lt;code&gt;pledge()&lt;/code&gt; library would need to get updated in sync&#xA;with glibc updates, or it would run the risk that glibc starts using a&#xA;syscall that it doesn&amp;rsquo;t allow-list, breaking the programs that use it.&lt;/p&gt;&#xA;&lt;p&gt;Justine Tunney&amp;rsquo;s &lt;code&gt;pledge()&lt;/code&gt; implementation works around these problems&#xA;by (a) only supporting her own, simpler, libc implementation, and (b)&#xA;only supporting the x86-64 architecture. I&amp;rsquo;m really happy to see that&#xA;this works well together, but I&amp;rsquo;m afraid it&amp;rsquo;s a mistake to think this&#xA;implementation can be &amp;ldquo;ported&amp;rdquo; to glibc which is used for the bulk of&#xA;Linux distributions.&lt;/p&gt;&#xA;&lt;h2 id=&#34;restricting-by-file-path&#34;&gt;Restricting by file path&lt;/h2&gt;&#xA;&lt;p&gt;In OpenBSD, &lt;code&gt;pledge()&lt;/code&gt; was always path-aware, until they moved that&#xA;part into the separate &lt;code&gt;unveil()&lt;/code&gt; call.&lt;/p&gt;&#xA;&lt;p&gt;Seccomp-bpf can only filter syscalls by their direct arguments, so the&#xA;filter can see the value of the pointer to the path name, but not the&#xA;path name itself in the memory referenced by that pointer.&lt;/p&gt;&#xA;&lt;p&gt;There are more advanced techniques to inspect pointer memory, but&#xA;using these safely involves separate supervisor processes or more&#xA;complicated constrained ways to control what processes do &amp;ndash; you need&#xA;to take security very seriously pull that off, and doesn&amp;rsquo;t map to a&#xA;call to a single C function like &lt;code&gt;pledge()&lt;/code&gt; anymore.&lt;/p&gt;&#xA;&lt;h2 id=&#34;landlock-promises-to-fix-this-in-the-future&#34;&gt;Landlock promises to fix this in the future&lt;/h2&gt;&#xA;&lt;p&gt;Unprivileged sandboxing continues to be difficult on Linux, for the&#xA;moment, and it&amp;rsquo;s no surprise that the main users of seccomp-bpf are&#xA;either dedicated sandboxing or containerization tools, or projects&#xA;where security is a major focus, like web browsers, OpenSSH or Tor.&#xA;But we should not give up yet. :)&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://landlock.io/&#34;&gt;Landlock LSM&lt;/a&gt; offers a better approach for&#xA;unprivileged sandboxing, although it can&amp;rsquo;t currently restrict the same&#xA;number of operations yet as seccomp-bpf can.  Landlock can&#xA;solve the above problems, because:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Landlock filters security-sensitive operations at the point when&#xA;these operations are done in the kernel, not at the system call&#xA;layer. This makes it architecture independent and removes the&#xA;need to keep up-to-date lists of system calls.&lt;/li&gt;&#xA;&lt;li&gt;Landlock can easily filter on file paths and other relevant&#xA;in-memory properties that can not be observed by seccomp-bpf at the&#xA;system call interface.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;If you want to try it out, Landlock is already enabled on some Linux&#xA;distributions (i.e. Arch Linux). A simple call to Landlock (using the&#xA;Go library) is:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;err := landlock.V1.BestEffort().RestrictPaths(&#xA;    landlock.RODirs(&amp;quot;/usr&amp;quot;, &amp;quot;/bin&amp;quot;),&#xA;    landlock.RWDirs(&amp;quot;/tmp&amp;quot;),&#xA;)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Some further links:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://landlock.io&#34;&gt;https://landlock.io&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://pkg.go.dev/github.com/landlock-lsm/go-landlock/landlock&#34;&gt;documentation for Go-Landlock&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;&#xA;&lt;p&gt;As shown above, seccomp-bpf makes it more difficult than necessary to&#xA;sandbox processes. It is available on a wide range of Linux&#xA;distributions, but it&amp;rsquo;s currently not practical to use for the bulk of&#xA;software linked to glibc, and it&amp;rsquo;s not possible to restrict operations&#xA;by file path in BPF.&lt;/p&gt;&#xA;&lt;p&gt;Landlock is not rolled out to all Linux distributions yet, and it&#xA;still has some known gaps in its current version, but it has a&#xA;significantly simpler API and a much simpler implementation in the&#xA;kernel than what would be required in userspace to work around the&#xA;problems of seccomp-bpf.&lt;/p&gt;&#xA;&lt;p&gt;And simplicity is a great property for security features to have. I&#xA;can wholeheartedly recommend having a look at it.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/pledge-on-linux/</guid>
      <pubDate>Sat, 16 Jul 2022 10:36:20 +0200</pubDate>
    </item>
    <item>
      <title>XKB With Sway</title>
      <link>https://blog.gnoack.org/post/xkb-with-sway</link>
      <description>&lt;p&gt;This explains how to configure multiple keyboards in Sway, and how to&#xA;use advanced configuration when regular &lt;code&gt;xkb_options&lt;/code&gt; are not enough.&lt;/p&gt;&#xA;&lt;h2 id=&#34;sway-configuration&#34;&gt;Sway configuration&lt;/h2&gt;&#xA;&lt;p&gt;Luckily, Sway has great support for multiple keyboards with different&#xA;layouts, so it nicely adjusts to whatever keyboard you have plugged in&#xA;or can use separate configurations for a physical keyboard and the&#xA;laptop keyboard.&lt;/p&gt;&#xA;&lt;p&gt;First, identify the identifiers for the keyboards you have plugged in,&#xA;using the following command:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ swaymsg -t get_inputs&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Look for the lines starting with &lt;code&gt;Identifier:&lt;/code&gt; for the device you&amp;rsquo;re&#xA;interested in.&lt;/p&gt;&#xA;&lt;p&gt;Next, add one section for each of your keyboards to &lt;code&gt;~/.config/sway/config&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;input &amp;quot;1278:32:PFU_Limited_HHKB-Classic&amp;quot; {&#xA;&#x9;xkb_layout &amp;quot;us&amp;quot;&#xA;&#x9;xkb_model &amp;quot;hhk&amp;quot;&#xA;&#x9;xkb_options &amp;quot;compose:ralt&amp;quot;&#xA;&#x9;xkb_capslock &amp;quot;disabled&amp;quot;&#xA;}&#xA;&#xA;input &amp;quot;1133:49948:Logitech_USB_Keyboard&amp;quot; {&#xA;&#x9;xkb_file &amp;quot;.xkb/keymap/logitech&amp;quot;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The example above lists a HHKB (&amp;ldquo;Happy Hacking&amp;rdquo;) keyboard where right&#xA;Alt acts as the Compose key. More options are documented in the man&#xA;pages &lt;a href=&#34;https://manned.org/sway-input.5&#34;&gt;sway-input(5)&lt;/a&gt; as well as&#xA;&lt;a href=&#34;https://manned.org/xkeyboard-config.7&#34;&gt;xkeyboard-config(7)&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;In the case where the existing XKB options are not sufficient, you&#xA;need to refer to a separate XKB configuration file, as described below.&lt;/p&gt;&#xA;&lt;h2 id=&#34;xkb-configuration&#34;&gt;XKB configuration&lt;/h2&gt;&#xA;&lt;p&gt;To configure my Logitech keyboard, the plan was to have the key&#xA;mapping reasonably close to the HHKB layout.&lt;/p&gt;&#xA;&lt;p&gt;In my case, I have two files under &lt;code&gt;~/.xkb&lt;/code&gt; (the directories need to&#xA;first be created): This is the &lt;code&gt;~/.xkb/keymap/logitech&lt;/code&gt; file:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;// Derived from `setxkbmap -print`,&#xA;// added the &amp;quot;+unixy&amp;quot; part to xkb_symbols.&#xA;xkb_keymap {&#xA;&#x9;xkb_keycodes  { include &amp;quot;evdev+aliases(qwerty)&amp;quot;&#x9;};&#xA;&#x9;xkb_types     { include &amp;quot;complete&amp;quot;&#x9;};&#xA;&#x9;xkb_compat    { include &amp;quot;complete&amp;quot;&#x9;};&#xA;&#x9;xkb_symbols   { include &amp;quot;pc+us+inet(evdev)+unixy&amp;quot;&#x9;};&#xA;&#x9;xkb_geometry  { include &amp;quot;pc(pc105)&amp;quot;&#x9;};&#xA;};&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This file is the output from running &lt;code&gt;setxkbmap -print&lt;/code&gt;, but adds the&#xA;&lt;code&gt;+unixy&lt;/code&gt; part to the &lt;code&gt;xkb_symbols&lt;/code&gt; section. This refers to the&#xA;&lt;code&gt;~/.xkb/symbols/unixy&lt;/code&gt; file with the following content:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;partial alphanumeric_keys&#xA;xkb_symbols &amp;quot;unixy&amp;quot; {&#xA;&#x9;// exchange backspace and backslash&#xA;&#x9;key &amp;lt;BKSL&amp;gt; {&#x9;[ BackSpace, Backspace ] };&#xA;&#x9;key &amp;lt;BKSP&amp;gt; {&#x9;[ backslash, bar ] };&#xA;&#xA;&#x9;// caps is ctrl&#xA;&#x9;key &amp;lt;CAPS&amp;gt; {&#x9;[ Control_L ] };&#xA;&#x9;modifier_map Control { &amp;lt;CAPS&amp;gt; };&#xA;&#xA;&#x9;// right alt is compose&#xA;&#x9;key &amp;lt;RALT&amp;gt; {&#x9;[ Multi_key ] };&#xA;};&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/xkb-with-sway/</guid>
      <pubDate>Thu, 02 Dec 2021 18:24:17 +0100</pubDate>
    </item>
    <item>
      <title>Error Handling Links</title>
      <link>https://blog.gnoack.org/post/error-handling-links</link>
      <description>&lt;p&gt;This is a collection of interesting literature on the subject of error&#xA;handling which I had collected to research &lt;a href=&#34;https://blog.gnoack.org/post/error_handling/&#34;&gt;my own blog post on the&#xA;subject&lt;/a&gt; a while ago.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&amp;ldquo;&lt;a href=&#34;https://www.amazon.com/Philosophy-Software-Design-John-Ousterhout/dp/1732102201&#34;&gt;A Philosophy of Software&#xA;Design&lt;/a&gt;&amp;rdquo;&#xA;by John Ousterhout has a significant section on error handling that&#xA;I found worth a read. It takes a slightly different angle and gives&#xA;some good examples on how to design programs in a way so that errors&#xA;cannot happen, or only happen in the right places.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://www.amazon.com/Smalltalk-80-Language-Implementation-Adele-Goldberg/dp/0201113716/&#34;&gt;Smalltalk-80 Blue&#xA;book&lt;/a&gt;&#xA;had the weirdest error handling mechanism: You&amp;rsquo;d call the &lt;code&gt;error:&lt;/code&gt;&#xA;method on your own object, and the interactive system pops up a&#xA;dialog. Some Smalltalk errors are recoverable in unusual ways&#xA;through the interactivity of the system - you can define methods on&#xA;the go if your program calls a method that doesn&amp;rsquo;t exist&amp;hellip; :)&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Common Lisp has a &amp;ldquo;Conditions&amp;rdquo; system which is similar to exceptions&#xA;in that it propagates up the stack, but it inserts a layer in the&#xA;middle between handling (&amp;ldquo;catching&amp;rdquo;) and signaling (&amp;ldquo;raising&amp;rdquo;)&#xA;exceptions. The middle layer can define recovery strategies which&#xA;the handling layer can then choose from.&lt;/p&gt;&#xA;&lt;p&gt;I found the Common Lisp stuff in the &amp;ldquo;&lt;a href=&#34;https://gigamonkeys.com/book/&#34;&gt;Practical Common&#xA;Lisp&lt;/a&gt;&amp;rdquo; book. I cannot claim that I&#xA;managed to wrap my head around that one. The chapter is online at&#xA;&lt;a href=&#34;https://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html&#34;&gt;https://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html&lt;/a&gt;&#xA;. Apparently someone wrote a full book on the Common Lisp condition&#xA;system recently, but I haven&amp;rsquo;t read that.&#xA;&lt;a href=&#34;https://amazon.com/Common-Lisp-Condition-System-Mechanisms/dp/148426133X&#34;&gt;https://amazon.com/Common-Lisp-Condition-System-Mechanisms/dp/148426133X&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Apart from that, the books I looked through were not very helpful. I&#xA;feel that the Ousterhout approach of explaining this is a useful one,&#xA;talking about ways to design programs so that the complications of&#xA;error handling are reduced.&lt;/p&gt;&#xA;&lt;p&gt;Other notable &amp;ldquo;error handling philosophies&amp;rdquo; I have only some links to&amp;hellip;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://python.org/dev/peps/pep-0020/&#34;&gt;https://python.org/dev/peps/pep-0020/&lt;/a&gt; The Zen of Python (&amp;ldquo;errors&#xA;should never pass silently&amp;rdquo;)&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The Midori error model&#xA;&lt;a href=&#34;http://joeduffyblog.com/2016/02/07/the-error-model/&#34;&gt;http://joeduffyblog.com/2016/02/07/the-error-model/&lt;/a&gt; (Midori was an&#xA;experimental OS developed at Microsoft)&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;My own research culminated in this article:&#xA;&lt;a href=&#34;https://blog.gnoack.org/post/error_handling/&#34;&gt;https://blog.gnoack.org/post/error_handling/&lt;/a&gt;. In hindsight, I took a&#xA;much too academic and prescriptive approach there, and so it doesn&amp;rsquo;t&#xA;get read much. The article might be biased towards the &amp;ldquo;deployed and&#xA;monitored&amp;rdquo; kind of software.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Another thing I heard people discuss is the rule to &amp;ldquo;act on an error&#xA;in only one place&amp;rdquo;, but I failed to find a canonical source for it.&#xA;(It&amp;rsquo;s the rule that is most commonly violated if someone is logging&#xA;the same error at multiple layers in the stack, leading to log spam.)&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/error-handling-links/</guid>
      <pubDate>Fri, 10 Sep 2021 16:54:54 +0200</pubDate>
    </item>
    <item>
      <title>Sandboxing with Landlock</title>
      <link>https://blog.gnoack.org/post/landlock</link>
      <description>&lt;p&gt;Linux 5.13 is going to include the unprivileged sandboxing feature&#xA;Landlock, which has been in the making for many years now.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;Unprivileged&lt;/em&gt; means that you don&amp;rsquo;t need special system-level&#xA;privileges like &lt;code&gt;root&lt;/code&gt; or &lt;code&gt;CAP_MAC_ADMIN&lt;/code&gt; to use it, as some other&#xA;Linux sandboxing mechanisms do. Philosophically speaking, you should&#xA;not need additional special privileges just to drop the privileges&#xA;that you already have.&lt;/p&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;How it works&lt;/div&gt;&#xA;&lt;p&gt;With Landlock, processes can restrict themselves to only use a&#xA;specified set of file paths, and it lets them control which operations&#xA;are available on these paths and their subdirectories (for example&#xA;opening for reading, writing, and directory operations&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;&#xA;&lt;p&gt;A similar sandboxing feature has been around in OpenBSD for a while&#xA;now with &lt;code&gt;unveil()&lt;/code&gt;: &lt;a href=&#34;https://www.openbsd.org/papers/bsdcan2019-unveil/index.html&#34;&gt;This slide&#xA;deck&lt;/a&gt;&#xA;&lt;a href=&#34;https://www.youtube.com/watch?v=gvmGfpMgny4&#34;&gt;and talk&lt;/a&gt; by Bob Beck&#xA;talks about the lessons learned and has examples on how to sandbox&#xA;software with it, which should translate well to Landlock.&lt;/p&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Download&lt;/div&gt;&#xA;&lt;p&gt;To make it easier to play with, I forked the Landlock example tool&#xA;from the kernel sources and made it compile standalone: You can get it&#xA;at&#xA;&lt;a href=&#34;https://github.com/gnoack/landlockjail&#34;&gt;https://github.com/gnoack/landlockjail&lt;/a&gt;,&#xA;or you can just download a &lt;a href=&#34;https://github.com/gnoack/landlockjail/releases/download/v1/lljail&#34;&gt;precompiled statically linked&#xA;version&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;At the moment, there are still some gaps, like &lt;code&gt;stat()&lt;/code&gt;,&#xA;which can still be done even in landlocked processes. But it&amp;rsquo;s moving&#xA;in the right direction, and at least accesses to the files&amp;rsquo; contents&#xA;can already be restricted.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/landlock/</guid>
      <pubDate>Fri, 16 Apr 2021 14:20:09 +0200</pubDate>
    </item>
    <item>
      <title>Writing Tests in Go</title>
      <link>https://blog.gnoack.org/post/go-tests</link>
      <description>&lt;div class=&#34;sidenote&#34;&gt;Tests should be correct by inspection&lt;/div&gt;&#xA;Tests require a different approach than normal code. We don&#39;t have&#xA;tests for tests, so tests need to be correct by inspection -- and the&#xA;main technique to achieve this is to get rid of the generality of the&#xA;production code, and exercise only very narrow and specific scenarios.&#xA;&lt;h1 id=&#34;decide-what-test-to-write&#34;&gt;Decide what test to write&lt;/h1&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;The goal of testing is to increase confidence&lt;/div&gt;&#xA;A test&#39;s purpose is to increase the confidence that you have in your&#xA;program&#39;s correctness. The next test to write is the test that&#xA;promises the highest ratio of additional confidence for the work&#xA;required to write it.&#xA;&lt;h2 id=&#34;do-test-all-your-business-decisions&#34;&gt;Do test all your business decisions&lt;/h2&gt;&#xA;&lt;p&gt;Test all your code that is testable. Prioritize testing the code that&#xA;(a) &lt;strong&gt;is important to work correctly&lt;/strong&gt; (where confidence needs to be&#xA;high) or (b) &lt;strong&gt;has become too complicated&lt;/strong&gt; (where confidence has&#xA;dropped too low).&lt;/p&gt;&#xA;&lt;h2 id=&#34;dont-test-io-and-side-effects&#34;&gt;Don&amp;rsquo;t test I/O and side effects&lt;/h2&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Controversial part ahead!&lt;/div&gt;&#xA;Sometimes, production code will do I/O or other side effects that are&#xA;hard to simulate reproducably in a test. In these cases, it can&#xA;sometimes be advisable to separate the I/O parts of the production&#xA;code out and only test the rest - the bulk of your logic. It&#39;s key to&#xA;only separate out only the smallest possible part that has the side&#xA;effect, to keep as much as possible of the program testable.&#xA;&lt;p&gt;&amp;ldquo;But Günther&amp;rdquo;, I hear you saying, &amp;ldquo;what if there is a bug in these I/O&#xA;parts&amp;rdquo;? Well - that&amp;rsquo;s why you need to keep them tiny and stare at them&#xA;very long in a code review, to make sure they work without a test. It&#xA;sounds heretic to leave this untested, but the alternative is to keep&#xA;the I/O and other program logic together. That&amp;rsquo;ll easily make your&#xA;more regular program logic hard to test, and that&amp;rsquo;s often not worth&#xA;the trade off. After all, our goal is to increase confidence, not to&#xA;reach full line coverage.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Special case:&lt;/strong&gt; There are also some types of I/O which don&amp;rsquo;t affect&#xA;your program logic, such as logging and incrementing performance&#xA;counters. It&amp;rsquo;s ok to leave those untested without separating them&#xA;out - these are battle proof APIs designed for quickly adding and&#xA;removing them from your code, and they do not play a role for your&#xA;programs correctness. It would give you little additional confidence&#xA;to test them.&lt;/p&gt;&#xA;&lt;h1 id=&#34;structure-your-tests-into-givenwhenthen&#34;&gt;Structure your tests into Given/When/Then&lt;/h1&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Stick to the three test phases!&lt;/div&gt;&#xA;&lt;p&gt;Stick with the setup/exercise/verify style of tests. Some people call&#xA;this also&#xA;&lt;a href=&#34;https://martinfowler.com/bliki/GivenWhenThen.html&#34;&gt;Given/When/Then&lt;/a&gt;&#xA;or the &lt;a href=&#34;http://xunitpatterns.com/Four%20Phase%20Test.html&#34;&gt;&amp;ldquo;Four Phase&#xA;test&amp;rdquo;&lt;/a&gt; (counting&#xA;tear-down as separate phase).&lt;/p&gt;&#xA;&lt;p&gt;It does not matter what you call this pattern, but &amp;ldquo;Given/When/Then&amp;rdquo;&#xA;tends to read nice in comments. Delimiting the section explicitly with&#xA;comments is optional, but serves another useful purpose: You cannot&#xA;use helper functions that span multiple of these purposes. (Those&#xA;helper functions would be bad style.)&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func TestSpellOut(t *testing.T) {&#xA;  // Given&#xA;  s := speller.New(&amp;quot;en&amp;quot;)&#xA;&#xA;  // When&#xA;  got := s.SpellOut(42)&#xA;&#xA;  // Then&#xA;  want := &amp;quot;forty-two&amp;quot;&#xA;  if got != want {&#xA;    t.Errorf(&amp;quot;SpellOut(%q); got %q, want %q&amp;quot;, 42, got, want)&#xA;  }&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h1 id=&#34;test-helpers&#34;&gt;Test helpers&lt;/h1&gt;&#xA;&lt;p&gt;Test helper functions should be the primary way to share functionality&#xA;between tests.&lt;/p&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Each helper serves only one of the test phases&lt;/div&gt;&#xA;Make sure that each test helper helps with only one of the test&#xA;phases: setup, exercise or verify.&#xA;&lt;p&gt;In particular, resist the temptation to extract the whole test into a&#xA;helper method, so that you can call it with differing parameters. The&#xA;way this is done in Go instead is with &lt;a href=&#34;https://dave.cheney.net/2019/05/07/prefer-table-driven-tests&#34;&gt;table-driven&#xA;tests&lt;/a&gt;&#xA;and&#xA;&lt;a href=&#34;https://golang.org/pkg/testing/#hdr-Subtests_and_Sub_benchmarks&#34;&gt;&lt;code&gt;t.Run()&lt;/code&gt;&lt;/a&gt;&#xA;if needed.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;setup-helpers&#34;&gt;Setup helpers&lt;/h2&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;The checklist for setup helpers&lt;/div&gt;&#xA;A setup helper is called from a test&#39;s setup phase and should have the&#xA;following properties:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;It accepts a &lt;code&gt;testing.TB&lt;/code&gt;&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; as first parameter.&lt;/li&gt;&#xA;&lt;li&gt;It calls &lt;code&gt;t.Helper()&lt;/code&gt; at the start.&lt;/li&gt;&#xA;&lt;li&gt;It may not return an error, but will call &lt;code&gt;t.Fatal()&lt;/code&gt; on error.&#xA;&lt;ul&gt;&#xA;&lt;li&gt;It is ok to fail early in setup helpers.&lt;/li&gt;&#xA;&lt;li&gt;Tests don&amp;rsquo;t need to check errors manually, making them easier to read.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;If it sets up state that needs to be undone after the test, it calls&#xA;&lt;a href=&#34;/post/go-testing-cleanup&#34;&gt;&lt;code&gt;t.Cleanup()&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This is a helper method for setting up a &lt;code&gt;Speller&lt;/code&gt; object from the&#xA;previous example:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func setUpSpeller(t testing.TB, path string) speller.Speller {&#xA;  t.Helper()&#xA;&#xA;  s, err := speller.Load(path)&#xA;  if err != nil {&#xA;    t.Fatalf(&amp;quot;Could not set up speller: speller.Load(%q): %v&amp;quot;,&#xA;             path, err)&#xA;  }&#xA;&#xA;  t.Cleanup(s.Close)&#xA;  return s&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;exercise-helpers&#34;&gt;Exercise helpers&lt;/h2&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Don&#39;t.&lt;/div&gt;&#xA;Only introduce helpers for the test&#39;s execute phase if the invocation&#xA;is unbearable to read.&#xA;&lt;p&gt;It&amp;rsquo;s good practice to exercise the component under test directly&#xA;without such a helper. Using the component directly is a good test for&#xA;its API&amp;rsquo;s usability and will often uncover small possibilities for API&#xA;improvements in the process.&lt;/p&gt;&#xA;&lt;h2 id=&#34;verification-helpers&#34;&gt;Verification helpers&lt;/h2&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Alternatives to assertion libraries&lt;/div&gt;&#xA;Common Go style discourages the use of assertion helper libraries,&#xA;which are common in most other languages.&#xA;&lt;p&gt;While I don&amp;rsquo;t necessarily agree with that, the next best thing you can&#xA;do within the bounds of this style is to extract helpers that do the&#xA;necessary comparisons for you, if necessary. The ground rule I use is:&#xA;The &lt;code&gt;if&lt;/code&gt; should be part of the top-level test function, but the&#xA;condition in that check can call the helper.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;These helpers do not need to have &lt;code&gt;testing.TB&lt;/code&gt; passed in - they are&#xA;just regular utility functions.&lt;/li&gt;&#xA;&lt;li&gt;They should probably return a boolean, or other object representing&#xA;the result of the check (like&#xA;&lt;a href=&#34;https://godoc.org/github.com/google/go-cmp/cmp#Diff&#34;&gt;&lt;code&gt;cmp.Diff&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h1 id=&#34;general-advice-on-writing-tests&#34;&gt;General advice on writing tests&lt;/h1&gt;&#xA;&lt;p&gt;A lot of general advice from other testing frameworks is applicable to&#xA;Go too.&lt;/p&gt;&#xA;&lt;h2 id=&#34;make-each-test-exercise-a-specific-narrow-scenario&#34;&gt;Make each test exercise a specific narrow scenario&lt;/h2&gt;&#xA;&lt;p&gt;Each test should exercise a specific and narrow scenario. There should&#xA;be little overlap between tests. Any bug introduced in the code should&#xA;ideally only break a single test, which has narrow focus and is&#xA;related to the broken functionality.&lt;/p&gt;&#xA;&lt;h3 id=&#34;start-with-the-assertion&#34;&gt;Start with the assertion&lt;/h3&gt;&#xA;&lt;p&gt;Start writing tests with the assertion, and work from there upwards by&#xA;filling out the missing variables that aren&amp;rsquo;t defined yet. This&#xA;approach helps me to focus each test around one specific and narrow&#xA;behaviour that I want to test.&lt;/p&gt;&#xA;&lt;h3 id=&#34;one-test-for-the-happy-path-plus-one-for-each-error&#34;&gt;One test for the happy path, plus one for each error&lt;/h3&gt;&#xA;&lt;p&gt;With simple functions with inputs, outputs and errors, I like to have&#xA;one main test for the happy path, plus one for each error, whose test&#xA;input is based on the one for the happy path, with a modification.&lt;/p&gt;&#xA;&lt;p&gt;See my article on &lt;a href=&#34;/post/base_fixture&#34;&gt;Shared base fixtures&lt;/a&gt; for examples.&lt;/p&gt;&#xA;&lt;h2 id=&#34;avoid-testmain-use-setup-helper-functions&#34;&gt;Avoid &lt;code&gt;TestMain&lt;/code&gt;, use setup helper functions&lt;/h2&gt;&#xA;&lt;p&gt;If you don&amp;rsquo;t know what &lt;code&gt;TestMain&lt;/code&gt; is, good!&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;TestMain&lt;/code&gt; is a way to share common test setup and teardown between&#xA;multiple test cases, without them calling a setup helper explicitly.&#xA;In most cases, this is better done with a setup helper function as&#xA;described above.&lt;/p&gt;&#xA;&lt;p&gt;There is one case where &lt;code&gt;TestMain&lt;/code&gt; is acceptable, which is the case&#xA;where the test setup is so expensive to run that it will be&#xA;significantly faster to only do it once.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;code&gt;t.Run()&lt;/code&gt; is a way of structuring your test output, but it&amp;rsquo;ll&#xA;also make sure that subtests can cancel with &lt;code&gt;t.Fatal()&lt;/code&gt;, without&#xA;affecting the execution of other subtests.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;&lt;code&gt;testing.TB&lt;/code&gt; is an interface for the common methods in&#xA;&lt;code&gt;testing.T&lt;/code&gt; and &lt;code&gt;testing.B&lt;/code&gt;, so it can be used from tests and&#xA;benchmarks alike.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/go-tests/</guid>
      <pubDate>Wed, 10 Feb 2021 22:00:06 +0100</pubDate>
    </item>
    <item>
      <title>Don&#39;t check emptyness before loops</title>
      <link>https://blog.gnoack.org/post/dont-check-emptyness-before-loops</link>
      <description>&lt;p&gt;If your plan is to loop over a collection: You shouldn&amp;rsquo;t check for it being non-empty first.&lt;/p&gt;&#xA;&lt;p&gt;Don&amp;rsquo;t do this:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;if (!stuff.empty()) {&#xA;  for (Item item : stuff) {&#xA;    // ...&#xA;  }&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Do this instead:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;for (Item item : stuff) {&#xA;  // ...&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Benefits:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Shorter&lt;/li&gt;&#xA;&lt;li&gt;Less nesting&lt;/li&gt;&#xA;&lt;li&gt;Same performance&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I used to do this too at some point in the past, because I thought&#xA;this would have performance benefits, but in reality, the performance&#xA;benefit is hardly measurable and generally falls into the category&#xA;&lt;a href=&#34;https://wiki.c2.com/?PrematureOptimization&#34;&gt;&amp;ldquo;premature optimization&amp;rdquo;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Even if there is a little extra code happening between the &lt;code&gt;if&lt;/code&gt; and&#xA;the &lt;code&gt;for&lt;/code&gt;, the performance impact needs to be significant for this to&#xA;make a difference in most cases, and it&amp;rsquo;s probably not worth the&#xA;additional code convolution.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/dont-check-emptyness-before-loops/</guid>
      <pubDate>Tue, 06 Oct 2020 07:12:04 +0200</pubDate>
    </item>
    <item>
      <title>Go-style coroutines in the FN language</title>
      <link>https://blog.gnoack.org/post/coroutines-in-fn</link>
      <description>&lt;p&gt;My toy Lisp &lt;a href=&#34;https://github.com/gnoack/fn&#34;&gt;fn&lt;/a&gt; is an inherently&#xA;single-threaded language, but with its built-in stack-inspection&#xA;capabilities, it&amp;rsquo;s easy to build coroutines on top.&lt;/p&gt;&#xA;&lt;p&gt;The&#xA;&lt;a href=&#34;https://github.com/gnoack/fn/blob/master/examples/threads.fn#L29&#34;&gt;implementation&lt;/a&gt;&#xA;only has about 20 lines of code, including comments. This article&#xA;contains a simplified implementation in-line. In only rewrote some&#xA;comments to make more sense in context, and removed confusing&#xA;technicalities like &lt;code&gt;fn&lt;/code&gt;&amp;rsquo;s custom method call syntax, the use of&#xA;&lt;code&gt;dynamic-wind&lt;/code&gt; and remarks about tail calls.&lt;/p&gt;&#xA;&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;&#xA;&lt;p&gt;In most programming languages, the call stack is a chain of stack&#xA;frames which ends in the program&amp;rsquo;s entry point.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;div id=&#39;pikchr-0&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:335px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 335.463 180.386&#34;&gt;&#xA;&lt;path d=&#34;M129.719,178.226L328.144,178.226L328.144,22.32L129.719,22.32Z&#34;  style=&#34;fill:rgb(255,228,196);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,178.226L58.8529,178.226L58.8529,149.879L2.16,149.879Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;30.5065&#34; y=&#34;164.052&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;bar&lt;/text&gt;&#xA;&lt;polygon points=&#34;30.5065,110.194 34.8265,121.714 26.1865,121.714&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M30.5065,149.879L30.5065,115.954&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;30.5065&#34; y=&#34;120.353&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; font-size=&#34;80%&#34; transform=&#34;rotate(-90 30.5065,130.037)&#34; dominant-baseline=&#34;central&#34;&gt;caller&lt;/text&gt;&#xA;&lt;path d=&#34;M2.16,110.194L58.8529,110.194L58.8529,81.8476L2.16,81.8476Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;30.5065&#34; y=&#34;96.0208&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;foo&lt;/text&gt;&#xA;&lt;polygon points=&#34;30.5065,42.1625 34.8265,53.6825 26.1865,53.6825&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M30.5065,81.8476L30.5065,47.9225&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;30.5065&#34; y=&#34;52.321&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; font-size=&#34;80%&#34; transform=&#34;rotate(-90 30.5065,62.005)&#34; dominant-baseline=&#34;central&#34;&gt;caller&lt;/text&gt;&#xA;&lt;path d=&#34;M2.16,42.1625L58.8529,42.1625L58.8529,13.8161L2.16,13.8161Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;30.5065&#34; y=&#34;27.9893&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;main&lt;/text&gt;&#xA;&lt;path d=&#34;M143.892,107.359L313.971,107.359L313.971,36.4932L143.892,36.4932Z&#34;  style=&#34;fill:rgb(255,228,196);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;228.932&#34; y=&#34;51.7663&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;information to&lt;/text&gt;&#xA;&lt;text x=&#34;228.932&#34; y=&#34;71.9263&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;resume execution of&lt;/text&gt;&#xA;&lt;text x=&#34;228.932&#34; y=&#34;92.0863&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;the calling function&lt;/text&gt;&#xA;&lt;path d=&#34;M143.892,164.052L313.971,164.052L313.971,107.359L143.892,107.359Z&#34;  style=&#34;fill:rgb(255,228,196);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;228.932&#34; y=&#34;125.626&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;values of local&lt;/text&gt;&#xA;&lt;text x=&#34;228.932&#34; y=&#34;145.786&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;variables and arguments&lt;/text&gt;&#xA;&lt;text x=&#34;228.932&#34; y=&#34;12.24&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;stack frame (simplified)&lt;/text&gt;&#xA;&lt;path d=&#34;M58.8529,178.226L129.719,178.226&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;path d=&#34;M58.8529,149.879L129.719,22.32&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:2.16,7.2;&#34; /&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;figcaption&gt;A conventional call stack&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;&#xA;Stack frames are on the heap in fn.&#xA;&lt;/div&gt;&#xA;&lt;p&gt;In the case of C, this call stack is a contiguous memory area, but in&#xA;&lt;em&gt;fn&lt;/em&gt;, the stack frames are actually allocated objects on the heap&#xA;which have pointers to each other. Each stack frame also stores the&#xA;information required to &lt;a href=&#34;https://github.com/gnoack/fn/blob/def2f2164f708df2980364f8f1495e94d0df8690/runtime/interpreter.c#L154&#34;&gt;resume&#xA;it&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;&#xA;Functions can get a handle on their own stack frame&#xA;&lt;/div&gt;&#xA;&lt;p&gt;Additionally, &lt;em&gt;fn&lt;/em&gt; has a natively implemented helper called&#xA;&lt;a href=&#34;https://github.com/gnoack/fn/blob/def2f2164f708df2980364f8f1495e94d0df8690/runtime/primitives.c#L311&#34;&gt;&lt;code&gt;$get-frame&lt;/code&gt;&lt;/a&gt;.&#xA;When called, &lt;code&gt;$get-frame&lt;/code&gt; returns the caller&amp;rsquo;s own stack frame as Lisp&#xA;object which can be manipulated. You can access it from an interactive&#xA;&lt;code&gt;fn&lt;/code&gt; session as well:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-scheme&#34;&gt;fn&amp;gt; ($get-frame)&#xA;#&amp;lt;Frame () @1ff4437b5780&amp;gt;&#xA;fn&amp;gt; (frame-caller ($get-frame))&#xA;#&amp;lt;Frame () @1ff4437b97c4&amp;gt;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;implementation&#34;&gt;Implementation&lt;/h2&gt;&#xA;&lt;h3 id=&#34;a-queue-of-suspended-coroutines&#34;&gt;A queue of suspended coroutines&lt;/h3&gt;&#xA;&lt;p&gt;For Go-style coroutine behaviour, we keep track of a queue of call&#xA;stacks of currently suspended coroutines. A coroutine which yields&#xA;execution queues itself last, and returns into the first call stack&#xA;from that queue.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;div id=&#39;pikchr-1&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:422px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 422.422 210.29&#34;&gt;&#xA;&lt;text x=&#34;39.6576&#34; y=&#34;177.89&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;pop out&lt;/text&gt;&#xA;&lt;text x=&#34;39.6576&#34; y=&#34;198.05&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;(resume)&lt;/text&gt;&#xA;&lt;polygon points=&#34;82.9152,187.97 94.4352,183.65 94.4352,192.29&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M88.6752,187.97L116.931,187.97&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M116.931,202.143L162.285,202.143L162.285,173.797L116.931,173.797Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;139.608,145.45 143.928,156.97 135.288,156.97&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M139.608,173.797L139.608,151.21&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M122.6,145.45L156.616,145.45L156.616,122.773L122.6,122.773Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;139.608,94.4266 143.928,105.947 135.288,105.947&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M139.608,122.773L139.608,100.187&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M122.6,94.4266L156.616,94.4266L156.616,71.7494L122.6,71.7494Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;139.608,43.403 143.928,54.923 135.288,54.923&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M139.608,71.7494L139.608,49.163&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M122.6,43.403L156.616,43.403L156.616,20.7258L122.6,20.7258Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;122.6&#34; y=&#34;73.008&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; transform=&#34;rotate(-90 122.6,83.088)&#34; dominant-baseline=&#34;central&#34;&gt;next to be resumed&lt;/text&gt;&#xA;&lt;path d=&#34;M162.285,202.143L207.64,202.143L207.64,173.797L162.285,173.797Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;184.962,145.45 189.282,156.97 180.642,156.97&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M184.962,173.797L184.962,151.21&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M167.955,145.45L201.97,145.45L201.97,122.773L167.955,122.773Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;184.962,94.4266 189.282,105.947 180.642,105.947&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M184.962,122.773L184.962,100.187&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M167.955,94.4266L201.97,94.4266L201.97,71.7494L167.955,71.7494Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M207.64,202.143L252.994,202.143L252.994,173.797L207.64,173.797Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;230.317,145.45 234.637,156.97 225.997,156.97&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M230.317,173.797L230.317,151.21&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M213.309,145.45L247.325,145.45L247.325,122.773L213.309,122.773Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;230.317,94.4266 234.637,105.947 225.997,105.947&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M230.317,122.773L230.317,100.187&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M213.309,94.4266L247.325,94.4266L247.325,71.7494L213.309,71.7494Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;230.317,43.403 234.637,54.923 225.997,54.923&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M230.317,71.7494L230.317,49.163&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M213.309,43.403L247.325,43.403L247.325,20.7258L213.309,20.7258Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M252.994,202.143L298.348,202.143L298.348,173.797L252.994,173.797Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;275.671,145.45 279.991,156.97 271.351,156.97&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M275.671,173.797L275.671,151.21&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M258.663,145.45L292.679,145.45L292.679,122.773L258.663,122.773Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;275.671,94.4266 279.991,105.947 271.351,105.947&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M275.671,122.773L275.671,100.187&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M258.663,94.4266L292.679,94.4266L292.679,71.7494L258.663,71.7494Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;298.348,187.97 309.868,183.65 309.868,192.29&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M304.108,187.97L332.364,187.97&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;379.193&#34; y=&#34;177.89&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;push in&lt;/text&gt;&#xA;&lt;text x=&#34;379.193&#34; y=&#34;198.05&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;(suspend)&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;figcaption&gt;A queue of suspended call stacks&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;&#xA;Starting new coroutines&#xA;&lt;/div&gt;&#xA;&lt;p&gt;A new coroutine is started with &lt;code&gt;thread-new!&lt;/code&gt;. &lt;code&gt;thread-new!&lt;/code&gt; suspends&#xA;its caller and itself becomes the entry point for the new coroutine&#xA;which immediately executes. The helper function &lt;code&gt;thread-die!&lt;/code&gt;&#xA;unschedules the current thread from execution by scheduling another&#xA;coroutine without adding itself back to the queue.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-scheme&#34;&gt;(defn thread-new! (thunk)&#xA;  ;; Suspend current call stack.&#xA;  (queue-push! *thread-queue* (frame-caller ($get-frame)))&#xA;  ;; Make sure we don&#39;t accidentally&#xA;  (set-frame-caller! ($get-frame) &#39;*should-never-be-used*)&#xA;  ;; Call thunk, then call thread-die!.&#xA;  (thunk)&#xA;  (thread-die!))&#xA;&#xA;(defn thread-die! ()&#xA;  (set-frame-caller! ($get-frame)&#xA;                     (queue-pop! *thread-queue*))&#xA;  nil)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h3 id=&#34;switching-between-coroutines&#34;&gt;Switching between coroutines&lt;/h3&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;&#xA;In some languages, &lt;tt&gt;next-thread!&lt;/tt&gt; would be called &lt;tt&gt;yield&lt;/tt&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;To switch between coroutines, all you need to do is to have &lt;em&gt;multiple&#xA;call stacks&lt;/em&gt; and switch between them. In &lt;em&gt;fn&lt;/em&gt;, this is done in the&#xA;&lt;code&gt;next-thread!&lt;/code&gt; function. &lt;code&gt;next-thread!&lt;/code&gt;, when called, will swizzle out&#xA;the calling stack frame stored in its own stack frame. That way, the&#xA;function can return into a different call stack than the one it was&#xA;invoked from.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;div id=&#39;pikchr-2&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:185px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 185.737 219.753&#34;&gt;&#xA;&lt;path d=&#34;M24.8372,217.593L115.546,217.593L115.546,189.247L24.8372,189.247Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;70.1915&#34; y=&#34;203.42&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;next-thread!&lt;/text&gt;&#xA;&lt;polygon points=&#34;30.5065,143.892 34.8265,155.412 26.1865,155.412&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M58.8529,189.247 L 44.6797,180.743 Q 30.5065,172.239 30.5065,160.946 L 30.5065,149.652&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,143.892L58.8529,143.892L58.8529,115.546L2.16,115.546Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;30.5065,87.1994 34.8265,98.7194 26.1865,98.7194&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M30.5065,115.546L30.5065,92.9594&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,87.1994L58.8529,87.1994L58.8529,58.8529L2.16,58.8529Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;30.5065,30.5065 34.8265,42.0265 26.1865,42.0265&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M30.5065,58.8529L30.5065,36.2665&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M2.16,30.5065L58.8529,30.5065L58.8529,2.16L2.16,2.16Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M16.3332,166.569L56.0183,166.569&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(255,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M16.3332,177.908L56.0183,155.231&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(255,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;56.0183&#34; y=&#34;155.231&#34; text-anchor=&#34;start&#34; fill=&#34;rgb(255,0,0)&#34; dominant-baseline=&#34;central&#34;&gt; 1.&lt;/text&gt;&#xA;&lt;polygon points=&#34;155.231,143.892 159.551,155.412 150.911,155.412&#34; style=&#34;fill:rgb(60,179,113)&#34;/&gt;&#xA;&lt;path d=&#34;M92.8687,189.247 L 124.05,177.908 Q 155.231,166.569 155.231,158.111 L 155.231,149.652&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(60,179,113);stroke-dasharray:7.2,7.2;&#34; /&gt;&#xA;&lt;text x=&#34;124.05&#34; y=&#34;154.869&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(60,179,113)&#34; dominant-baseline=&#34;central&#34;&gt;2.&lt;/text&gt;&#xA;&lt;path d=&#34;M126.884,143.892L183.577,143.892L183.577,115.546L126.884,115.546Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;155.231,87.1994 159.551,98.7194 150.911,98.7194&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M155.231,115.546L155.231,92.9594&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M126.884,87.1994L183.577,87.1994L183.577,58.8529L126.884,58.8529Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;155.231,30.5065 159.551,42.0265 150.911,42.0265&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M155.231,58.8529L155.231,36.2665&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M126.884,30.5065L183.577,30.5065L183.577,2.16L126.884,2.16Z&#34;  style=&#34;fill:rgb(173,216,230);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;figcaption&gt;next-thread! returns into a different call stack than it was called from&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;&lt;code&gt;next-thread!&lt;/code&gt; is implemented as follows:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-scheme&#34;&gt;(defn next-thread! ()&#xA;  ;; Push our own frame&#39;s caller onto the queue.&#xA;  (queue-push! *thread-queue* (frame-caller ($get-frame)))&#xA;  ;; Remove a suspended stack frame from the queue,&#xA;  ;; and set it as our own caller, so we&#39;ll return to it.&#xA;  (set-frame-caller! ($get-frame)&#xA;                     (queue-pop! *thread-queue*))&#xA;  nil)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h3 id=&#34;caveats-about-tail-calls&#34;&gt;Caveats about tail calls&lt;/h3&gt;&#xA;&lt;p&gt;Manipulating your own stack frame leads to a simple and short&#xA;implementation, but the devil is in the details.&lt;/p&gt;&#xA;&lt;p&gt;In particular, in languages with implicit and automatic tail calls, an&#xA;invocation such as &lt;code&gt;(set-frame-caller! ($get-frame) ...)&lt;/code&gt; cannot be&#xA;put at the end of a function, of you would manipulate the caller of a&#xA;function that won&amp;rsquo;t ever return again. To work around this problem,&#xA;functions like &lt;code&gt;next-thread!&lt;/code&gt; and &lt;code&gt;thread-die!&lt;/code&gt; return &lt;code&gt;nil&lt;/code&gt;&#xA;explicitly at the end, so that &lt;code&gt;set-frame-caller!&lt;/code&gt; does not get&#xA;invoked with a tail call.&lt;/p&gt;&#xA;&lt;p&gt;The full coroutine implementation is in the &lt;code&gt;fn&lt;/code&gt; repository in&#xA;&lt;a href=&#34;https://github.com/gnoack/fn/blob/master/examples/threads.fn&#34;&gt;&lt;code&gt;examples/threads.fn&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/coroutines-in-fn/</guid>
      <pubDate>Mon, 14 Sep 2020 11:15:00 +0100</pubDate>
    </item>
    <item>
      <title>Rendering drawings with tuhirender</title>
      <link>https://blog.gnoack.org/post/tuhirender</link>
      <description>&lt;p&gt;Recently, almost all my digital drawings were made with a &lt;a href=&#34;https://www.amazon.de/Wacom-Bamboo-Slate-Smartpad-Digitalisierungs-Funktion/dp/B01L1V61MC&#34;&gt;Bamboo&#xA;Slate&lt;/a&gt;&#xA;tablet and &lt;a href=&#34;https://github.com/tuhiproject/tuhi&#34;&gt;Tuhi&lt;/a&gt;. It was good&#xA;fun to take Tuhi&amp;rsquo;s drawing data and create my own renderer&#xA;&lt;a href=&#34;https://github.com/gnoack/tuhirender&#34;&gt;tuhirender&lt;/a&gt;, which can be used&#xA;from the command line to produce some more advanced effects and tune&#xA;more of the rendering knobs.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;tuhirender&lt;/code&gt; replaces Tuhi&amp;rsquo;s own renderer by building on Tuhi&amp;rsquo;s&#xA;JSON-based output format for line coordinates and pen pressure data.&lt;/p&gt;&#xA;&lt;h2 id=&#34;basic-invocation&#34;&gt;Basic invocation&lt;/h2&gt;&#xA;&lt;p&gt;The basic invocation is:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;tuhirender -width 200 -fit -o out.png &amp;lt; in/1593622352.json&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;All parameters are in principle optional, but this invocation shows&#xA;the most commonly used options. &lt;code&gt;-width&lt;/code&gt; specifies the desired output&#xA;width in pixels.&lt;/p&gt;&#xA;&lt;h2 id=&#34;animate-a-drawing&#34;&gt;Animate a drawing&lt;/h2&gt;&#xA;&lt;p&gt;To animate the drawing progress, use the &lt;code&gt;gif&lt;/code&gt; output format by passing the flag &lt;code&gt;--format gif&lt;/code&gt; and specifying a matching filename with &lt;code&gt;-o out.gif&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/drawing.gif&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;An animated drawing&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;h2 id=&#34;simplify-line-segments&#34;&gt;Simplify line segments.&lt;/h2&gt;&#xA;&lt;p&gt;With the &lt;code&gt;--simplify&lt;/code&gt; flag, &lt;code&gt;tuhirender&lt;/code&gt; simplifies all lines. See my&#xA;previuos post on the topic: &lt;a href=&#34;https://blog.gnoack.org/post/path-simplification-lib/&#34;&gt;Go: Path simplification&#xA;library&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/hippie.png&#34; alt=&#34;An original and a path-simplified picture side-by-side&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;Original and simplified version side-by-side&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;This picture is a bit of an extreme example to demonstrate the&#xA;simplification. When tweaking the parameters a bit, the simplification&#xA;is less visible. At some point, I might be able to use this for&#xA;outputting vector graphics at reduced file size and acceptable quality&#xA;loss.&lt;/p&gt;&#xA;&lt;h2 id=&#34;some-sample-pictures&#34;&gt;Some sample pictures&lt;/h2&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/winged-victory-of-samothrace.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;A bigger picture: Winged Victory of Samothrace&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;This is the biggest picture I&amp;rsquo;ve drawn so far with the tablet; I&amp;rsquo;m&#xA;happy with how it turned out. The hatching renders nicely and produces&#xA;the desired shades.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/sausage.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;A cartoon drawing: A man, a dog and a sausage&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;This cartoon drawing is simpler and has a bigger zoom, so it&amp;rsquo;s easier&#xA;to see how the change in pen pressure is rendered.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/gopher.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;A gopher&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;The gopher picture has a lot of nuanced Pen pressure, and arguably it&#xA;looked slightly better on paper. At the moment, &lt;code&gt;tuhirender&lt;/code&gt; renders&#xA;pen pressure by modifying the line width on the go. I suspect that&#xA;changing the line opacity might be better, but I&amp;rsquo;m still looking for&#xA;an elegant algorithm to fix up the artifacts between line segments&#xA;when doing that.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/tuhirender/</guid>
      <pubDate>Mon, 06 Jul 2020 00:04:10 +0200</pubDate>
    </item>
    <item>
      <title>Go: Test Cleanup in Go 1.14</title>
      <link>https://blog.gnoack.org/post/go-testing-cleanup</link>
      <description>&lt;p&gt;In Go 1.14, the new &lt;a href=&#34;https://godoc.org/testing#T.Cleanup&#34;&gt;T.Cleanup()&lt;/a&gt;&#xA;function schedules defer-like work to be done after the current test.&#xA;This makes it possible to write very comfortable setup helper&#xA;functions:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-golang&#34;&gt;func SetUpFrobnicator(t *testing.T) *frob.Frobnicator {&#xA;    t.Helper()&#xA;&#xA;    f := frob.New()    // Do setup.&#xA;    t.Cleanup(f.Close) // Schedule cleanup for later.&#xA;    return f&#xA;}&#xA;&#xA;func TestFoobar(t *testing.T) {&#xA;    f := SetUpFrobnicator(t)&#xA;&#xA;    // (Run the test itself, using f.)&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Previously, presumably the most idiomatic thing would have been to&#xA;return a &amp;ldquo;cancel&amp;rdquo; callback from the helper function and rely on the&#xA;test to defer it.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m almost a bit surprised that the Go test framework offers this&#xA;now - otherwise, compared to other languages, Go&amp;rsquo;s test code is very&#xA;similar in style to production code (e.g. it doesn&amp;rsquo;t even have assert&#xA;functions).&lt;/p&gt;&#xA;&lt;p&gt;Related articles on this blog:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;/post/setup-cleanup/&#34;&gt;The Setup-Cleanup problem&lt;/a&gt; in different programming languages&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;/post/base_fixture/&#34;&gt;Shared Base Fixture&lt;/a&gt; in unit tests&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/go-testing-cleanup/</guid>
      <pubDate>Thu, 14 May 2020 09:26:41 +0200</pubDate>
    </item>
    <item>
      <title>Why Software Design?</title>
      <link>https://blog.gnoack.org/post/why-software-design</link>
      <description>&lt;p&gt;The main technique in software design is this: You look at the&#xA;entirety of your system and you decompose it into pieces that are more&#xA;manageable and have clear interfaces. This approach is usually&#xA;referred to as &lt;em&gt;modularization&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;p&gt;But what&amp;rsquo;s the point of investing such an effort if in the end only&#xA;the externally visible properties of a software matter?&lt;/p&gt;&#xA;&lt;p&gt;In his classic paper “On the Criteria To Be Used in Decomposing&#xA;Systems into Modules”&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, David Parnas gives the following&#xA;rationale:&lt;/p&gt;&#xA;&lt;figure class=&#34;alignright&#34;&gt;&#xA;&lt;img src=&#34;/images/parnas.png&#34; alt=&#34;David Parnas&#34;/&gt;&#xA;&lt;figcaption&gt;David Parnas&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;The benefits expected of modular&#xA;programming are (1) &lt;em&gt;managerial&lt;/em&gt; &amp;ndash; development time should be&#xA;shortened because separate groups would work on each module with&#xA;little need for communication; (2) &lt;em&gt;product flexibility&lt;/em&gt; &amp;ndash; it&#xA;should be possible to make drastic changes to one module without a&#xA;need to change others; (3) &lt;em&gt;comprehensibility&lt;/em&gt; &amp;ndash; it should be&#xA;possible to study the system one module at a time. The whole system&#xA;can therefore be better designed because it is better understood.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;To paraphrase this in today&amp;rsquo;s terminology, the benefits of modularization are:&lt;/p&gt;&#xA;&lt;p&gt;(1) &lt;strong&gt;To enable independent work&lt;/strong&gt; on different modules through clear&#xA;interfaces which reduce communication overhead.&lt;/p&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Conway&#39;s Law&lt;/div&gt;&#xA;&lt;p&gt;A more sarcastic take on cross-team communication overhead is&#xA;&lt;a href=&#34;https://en.wikipedia.org/wiki/Conway%27s_law&#34;&gt;Conway&amp;rsquo;s Law&lt;/a&gt;, which&#xA;states that any system designed by an organization will reflect that&#xA;organization&amp;rsquo;s communication structure.&lt;/p&gt;&#xA;&lt;p&gt;(2) &lt;strong&gt;To simplify future changes&lt;/strong&gt;, by aligning the code so that anticipated&#xA;changes are easy.&lt;/p&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Need to anticipate changing requirements!&lt;/div&gt;&#xA;&lt;p&gt;Changes are easy when they are contained within a module or crossing&#xA;few module boundaries. The difficulty is to &lt;em&gt;identify&lt;/em&gt; likely future&#xA;changes and lay out the system accordingly, so that they will be a fit&#xA;with the design and won&amp;rsquo;t require changes to the bigger architecture.&lt;/p&gt;&#xA;&lt;p&gt;As David Parnas says himself in the conclusion to his paper:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;We propose instead that one begins with a list of difficult design&#xA;decisions or design decisions which are likely to change. Each&#xA;module is then designed to hide such a decision from the others.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Modules should not rely on each other beyond what their interfaces&#xA;guarantee, so that if you change a module, the change is contained&#xA;behind its interface and not observable to others.&lt;/p&gt;&#xA;&lt;p&gt;(3) &lt;strong&gt;To increase confidence in functionality&lt;/strong&gt;, by making reasoning&#xA;about relevant aspects easier.&lt;/p&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Easier reasoning for module users&lt;/div&gt;&#xA;&lt;p&gt;The key here is that the interface to each module should give a more&#xA;abstract guarantee which hides the module&amp;rsquo;s implementation details. If&#xA;the right abstraction is picked, the module&amp;rsquo;s users will be able to&#xA;reason about it in simpler terms and avoid dealing with details.&lt;/p&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Deep classes&lt;/div&gt;&#xA;&lt;p&gt;In a good modularization, the interface definition is small and the&#xA;contained functionality is large (a property which John Ousterhout&#xA;calls &amp;ldquo;Deep Classes&amp;rdquo;)&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;. This minimizes the effort to use a&#xA;module and maximizes its benefit.&lt;/p&gt;&#xA;&lt;p&gt;Besides reasoning about behavior, a good system decomposition can&#xA;also help reasoning about other aspects. For example, it&amp;rsquo;s easier to&#xA;reason about crash resilience if different parts of the system are&#xA;deployed as separate services.&lt;/p&gt;&#xA;&lt;div class=&#34;sidenote&#34;&gt;Easier reasoning within modules&lt;/div&gt;&#xA;&lt;p&gt;Finally, a suitable system decomposition also simplifies reasoning&#xA;&lt;em&gt;within&lt;/em&gt; a module, which now has a clear contract to fulfill, as&#xA;defined by the module&amp;rsquo;s interface.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;There is more to be said&lt;/strong&gt; about modularization techniques and&#xA;different approaches on how to deal with the uncertainties of changing&#xA;requirements, but that has to go on a follow-up article. Subscribe to&#xA;this blog with your RSS reader to get notified of future articles.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;David Parnas, &lt;a href=&#34;https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf&#34;&gt;On the Criteria To Be Used in Decomposing&#xA;Systems into Modules&lt;/a&gt; (1972)&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;John Ousterhout, &lt;a href=&#34;https://www.amazon.com/Philosophy-Software-Design-John-Ousterhout/dp/1732102201&#34;&gt;A Philosophy of Software&#xA;Design&lt;/a&gt; (2018)&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/why-software-design/</guid>
      <pubDate>Wed, 13 May 2020 21:10:10 +0200</pubDate>
    </item>
    <item>
      <title>Go: Sharing templates across multiple pages</title>
      <link>https://blog.gnoack.org/post/go_templates</link>
      <description>&lt;figure&gt;&#xA;&lt;img src=&#34;/images/gutengopher.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;Renaissance gopher Johannes Gopherberg after inventing html/template&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;I&amp;rsquo;m using &lt;code&gt;html/template&lt;/code&gt; much too seldom to remember how to&#xA;properly share common template blocks across multiple pages. Today I&#xA;had to figure it out for the second time. It&amp;rsquo;s about time to document&#xA;it!&lt;/p&gt;&#xA;&lt;p&gt;The approach presented here both (1) makes it possible to share&#xA;template blocks across multiple logical pages, and (2) makes it easy&#xA;to use them using &lt;code&gt;tmpl.Execute(writer, data)&lt;/code&gt; in your HTTP handlers.&lt;/p&gt;&#xA;&lt;p&gt;In particular, with this approach, all pages share a common top-level&#xA;definition of an HTML page, as opposed to maintaining separate header&#xA;and footer templates whose opening and closing tags would need to be&#xA;kept in sync.&lt;/p&gt;&#xA;&lt;p&gt;From the &lt;a href=&#34;https://golang.org/pkg/text/template/#Template.Clone&#34;&gt;text/template documentation&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Clone can be used to prepare common templates and use them with&#xA;variant definitions for other templates by adding the variants after&#xA;the clone is made.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;With &lt;code&gt;Clone()&lt;/code&gt;, you can have a separate &lt;code&gt;template.Template&lt;/code&gt; instance&#xA;for each of your pages, while making them share a common set of base&#xA;templates at the same time.&lt;/p&gt;&#xA;&lt;p&gt;The way you&amp;rsquo;d like to construct these goes something like this:&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/templates.svg&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;Constructing separate template collections from a shared base&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;None of this is really a secret. However, before &lt;code&gt;.Clone()&lt;/code&gt; existed,&#xA;it wasn&amp;rsquo;t as easy, and it&amp;rsquo;s still easy to find a lot of outdated&#xA;advice.&lt;/p&gt;&#xA;&lt;h2 id=&#34;example&#34;&gt;Example&lt;/h2&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s go step by step.&lt;/p&gt;&#xA;&lt;h3 id=&#34;directory-structure-for-this-example&#34;&gt;Directory structure for this example&lt;/h3&gt;&#xA;&lt;p&gt;The &lt;em&gt;base templates&lt;/em&gt; live in &lt;code&gt;templates/base/*.html&lt;/code&gt;. These include&#xA;&lt;code&gt;page.html&lt;/code&gt; for the top-level definition of an HTML file, a dummy&#xA;definition for the main page content in &lt;code&gt;main.html&lt;/code&gt;, as well as&#xA;various helpers that may be reused across various pages.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;em&gt;per-page templates&lt;/em&gt; live in &lt;code&gt;templates/${PAGENAME}/*.html&lt;/code&gt;. A&#xA;minimal page just redefines &lt;code&gt;main.html&lt;/code&gt; to have the necessary content.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/template_instantiation.svg&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;The templates in foo/ override main.html from base/.&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;h3 id=&#34;create-the-base-templates&#34;&gt;Create the base templates&lt;/h3&gt;&#xA;&lt;p&gt;First, define the base templates with the &lt;code&gt;page.html&lt;/code&gt; definition as&#xA;root template for all template instantiations.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;base := template.New(&amp;quot;page.html&amp;quot;)&#xA;base  = template.Must(&#xA;    base.ParseGlob(&amp;quot;templates/base/*.html&amp;quot;)))&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;We name the template collection &lt;code&gt;page.html&lt;/code&gt;, so that &lt;code&gt;page.html&lt;/code&gt; will&#xA;be used as the default template to render for all of our pages, when&#xA;it gets called through &lt;code&gt;.Execute()&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;code&gt;templates/base/page.html&lt;/code&gt; template defines the top-level HTML structure with&#xA;navigation bars and core site elements, and includes the main page&#xA;content &lt;code&gt;main.html&lt;/code&gt; with a &lt;code&gt;block&lt;/code&gt; action:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;html&amp;gt;&#xA;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;...&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&#xA;&amp;lt;body&amp;gt;&#xA;  &amp;lt;!-- navigation, main site elements, etc --&amp;gt;&#xA;  {{- block &amp;quot;main.html&amp;quot; . -}}{{- end -}}&#xA;&amp;lt;/body&amp;gt;&#xA;&amp;lt;/html&amp;gt;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h3 id=&#34;derive-the-specialized-templates-for-the-foo-handler&#34;&gt;Derive the specialized templates for the &lt;code&gt;foo&lt;/code&gt; handler&lt;/h3&gt;&#xA;&lt;p&gt;In the package or file for the &lt;code&gt;foo&lt;/code&gt; handler, create a local &lt;code&gt;var fooTmpl *template.Template&lt;/code&gt; where you derive the base template:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;fooTmpl := template.Must(baseTmpl.Clone())&#xA;fooTmpl  = template.Must(&#xA;    fooTmpl.ParseGlob(&amp;quot;templates/foo/*.html&amp;quot;))&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;We define page content in the &lt;code&gt;templates/foo/main.html&lt;/code&gt; template:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;&#xA;&amp;lt;p&amp;gt;This is an example template.&amp;lt;/p&amp;gt;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h3 id=&#34;use-the-specialized-templates-in-your-handler&#34;&gt;Use the specialized templates in your handler&lt;/h3&gt;&#xA;&lt;p&gt;Finally, all that&amp;rsquo;s needed to use the specialized templates in a&#xA;handler is to just call &lt;code&gt;.Execute()&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func handleFoo(w http.ResponseWriter, req *http.Request) {&#xA;        data := fooData{Key: &amp;quot;Value&amp;quot;}&#xA;        fooTmpl.Execute(w, data)&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It will automatically instantiate the collection of templates starting&#xA;from &lt;code&gt;page.html&lt;/code&gt;.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/go_templates/</guid>
      <pubDate>Sun, 03 May 2020 11:10:01 +0100</pubDate>
    </item>
    <item>
      <title>Go: Path simplification library</title>
      <link>https://blog.gnoack.org/post/path-simplification-lib</link>
      <description>&lt;p&gt;A few weeks ago, I got myself a &lt;a href=&#34;https://www.amazon.de/Wacom-Bamboo-Slate-Smartpad-Digitalisierungs-Funktion/dp/B01L1V61MC&#34;&gt;Bamboo&#xA;Slate&lt;/a&gt;&#xA;tablet, and had a lot of fun playing around with the output. The Linux&#xA;software for talking to that device is&#xA;&lt;a href=&#34;https://github.com/tuhiproject/tuhi&#34;&gt;Tuhi&lt;/a&gt;, which is GUI driven, but&#xA;also dumps the raw point and pressure data in JSON format into a&#xA;directory.&lt;/p&gt;&#xA;&lt;p&gt;As a side effect of playing around with that, I published a &lt;a href=&#34;https://github.com/gnoack/path&#34;&gt;2d path&#xA;simplification library&lt;/a&gt; which uses the&#xA;&lt;a href=&#34;https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm&#34;&gt;Ramer-Douglas-Peucker&#xA;algorithm&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Given a sequence of 2D points, the algorithm determines a subset of&#xA;these points so that the path drawn by these is still reasonably close&#xA;to the original. Here is an example output:&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/hippie.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;Original and simplified version side-by-side&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;I found the Ramer-Douglas-Peucker algorithm to be very clever, my&#xA;attempts to come up with my own algorithm had terrible results in&#xA;comparison.&lt;/p&gt;&#xA;&lt;p&gt;The library is here:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Source: &lt;a href=&#34;https://github.com/gnoack/path&#34;&gt;https://github.com/gnoack/path&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Godoc: &lt;a href=&#34;https://godoc.org/github.com/gnoack/path&#34;&gt;https://godoc.org/github.com/gnoack/path&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I have a vague idea to use this for simplifying diagram drawings and&#xA;straightening out imprecisely drawn shapes, replacing squiggly boxes&#xA;and arrows with properly aligned ones. I&amp;rsquo;m not sure whether I&amp;rsquo;m going&#xA;in the right direction with this approach, given that I&amp;rsquo;m not making&#xA;use of pressure data and my knowledge of the shapes I want to&#xA;recognize; we&amp;rsquo;ll see whether it&amp;rsquo;ll turn out to work.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/path-simplification-lib/</guid>
      <pubDate>Sun, 05 Apr 2020 14:13:52 +0200</pubDate>
    </item>
    <item>
      <title>Go: The lack of generics</title>
      <link>https://blog.gnoack.org/post/go-lack-of-generics</link>
      <description>&lt;p&gt;Go&amp;rsquo;s lack of generics means that people can&amp;rsquo;t overengineer in the way&#xA;they are used to, so they need to put more thought into their designs.&lt;/p&gt;&#xA;&lt;p&gt;There are some use cases where abstractions with generics are better&#xA;to the current ones (e.g. the &lt;code&gt;sort&lt;/code&gt; package), but by and large, in&#xA;other languages, people use them to build towers of abstractions which&#xA;you&amp;rsquo;ll later need to wrap your head around, which introduce conceptual&#xA;duplication (e.g. mocking and predicate frameworks) and which lead to&#xA;a fragmentation of Go style (as would be the case if people start&#xA;building abstractions to hide the &lt;code&gt;go&lt;/code&gt; keyword, like threading and&#xA;future libraries).&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/go-lack-of-generics/</guid>
      <pubDate>Sun, 05 Apr 2020 11:53:00 +0200</pubDate>
    </item>
    <item>
      <title>The Setup-Cleanup problem</title>
      <link>https://blog.gnoack.org/post/setup-cleanup</link>
      <description>&lt;figure&gt;&#xA;&lt;img src=&#34;/images/cleanup.png&#34; alt=&#34;A picture of the Sorcerer&amp;rsquo;s apprentice enchanting brooms to fetch water&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;A fully automated cleanup strategy.&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;This is a comparison of different programming idioms for performing&#xA;cleanup work.&lt;/p&gt;&#xA;&lt;p&gt;Common use cases where this is used include:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Locking and unlocking synchronization locks.&lt;/li&gt;&#xA;&lt;li&gt;Opening and closing files.&lt;/li&gt;&#xA;&lt;li&gt;Canceling asynchronous operations that aren&amp;rsquo;t needed any more.&lt;/li&gt;&#xA;&lt;li&gt;Allocation and deallocation.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;a-naive-solution-cluttered-cleanups&#34;&gt;A naive solution: Cluttered cleanups&lt;/h2&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;int frobnicate() {&#xA;        set_up();&#xA;&#xA;        int err = foo();&#xA;        if (err) {&#xA;                clean_up();  // &amp;lt;-- 1.&#xA;                return err;&#xA;        }&#xA;&#xA;        if (!bar()) {&#xA;                clean_up();  // &amp;lt;-- 2.&#xA;                return ERROR_ACCESS;&#xA;        }&#xA;&#xA;        baz();&#xA;&#xA;        clean_up();          // &amp;lt;-- 3.&#xA;        return SUCCESS;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;There are now three places where &lt;code&gt;clean_up()&lt;/code&gt; is being called (Code&#xA;duplication), and they are all far away from the call to &lt;code&gt;set_up()&lt;/code&gt;.&#xA;When the code changes a bit, it&amp;rsquo;s easy to miss one and introduce&#xA;cleanup issues. This is not a well-maintainable approach.&lt;/p&gt;&#xA;&lt;h2 id=&#34;linux-kernel-style-c&#34;&gt;Linux kernel style (C)&lt;/h2&gt;&#xA;&lt;p&gt;The Linux kernel uses gotos to coordinate a function&amp;rsquo;s cleanup:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-c&#34;&gt;int frobnicate()&#xA;{&#xA;        int rc = 0;&#xA;&#xA;        set_up();&#xA;&#xA;        rc = foo();&#xA;        if (rc)&#xA;                goto out;&#xA;&#xA;        if (!bar()) {&#xA;                rc = EACCES;&#xA;                goto out;&#xA;        }&#xA;&#xA;        baz();&#xA;&#xA;out:&#xA;        clean_up();  // &amp;lt;--&#xA;        return rc;&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;All function exits happening after &lt;code&gt;set_up()&lt;/code&gt; need to jump to the&#xA;label where the &lt;code&gt;clean_up()&lt;/code&gt; is done.&lt;/p&gt;&#xA;&lt;p&gt;Note that this pattern can be nested: With multiple independent setup&#xA;and cleanup steps, the function ends with a sequence of multiple&#xA;cleanup operations and multiple exit labels&#xA;(&lt;a href=&#34;https://github.com/torvalds/linux/blob/v5.4/fs/afs/rxrpc.c#L362&#34;&gt;example&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;p&gt;A longer explanation is at &lt;a href=&#34;https://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c&#34;&gt;Eli Bendersky&amp;rsquo;s&#xA;website&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;resource-acquisition-is-initialization-c&#34;&gt;Resource Acquisition is Initialization (C++)&lt;/h2&gt;&#xA;&lt;p&gt;In C++, setup and cleanup is frequently bound to object lifetimes,&#xA;which are deterministic there. This is an example using&#xA;&lt;a href=&#34;https://abseil.io&#34;&gt;Abseil&amp;rsquo;s&lt;/a&gt;&#xA;&lt;a href=&#34;https://abseil.io/docs/cpp/guides/synchronization#the-mutexlock-wrapper&#34;&gt;&lt;code&gt;absl::MutexLock&lt;/code&gt;&lt;/a&gt;&#xA;class:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-c++&#34;&gt;void MyClass::Frobnicate() {&#xA;  absl::MutexLock l(&amp;amp;mutex_);&#xA;&#xA;  Foo();  // under lock&#xA;  Bar();  // under lock&#xA;  Baz();  // under lock&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The lifetime of the &lt;code&gt;l&lt;/code&gt; variable ends with the function scope, and&#xA;&lt;code&gt;absl::MutexLock&lt;/code&gt;&amp;rsquo;s constructor and destructor are locking and&#xA;unlocking the mutex.&lt;/p&gt;&#xA;&lt;p&gt;This pattern can also be nested. C++ guarantees that variable&#xA;destruction happens in the inverse order of construction.&lt;/p&gt;&#xA;&lt;p&gt;The RAII pattern&#xA;(&lt;a href=&#34;https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization&#34;&gt;Wikipedia&lt;/a&gt;)&#xA;is also used in other languages like Rust (&lt;a href=&#34;https://doc.rust-lang.org/std/ops/trait.Drop.html&#34;&gt;&lt;code&gt;Drop&lt;/code&gt;&#xA;trait&lt;/a&gt;).&lt;/p&gt;&#xA;&lt;p&gt;To a similar extent, similar functionality to RAII is also available&#xA;in C, when using the GCC-specific&#xA;&lt;a href=&#34;https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-cleanup-variable-attribute&#34;&gt;&lt;code&gt;__attribute__((__cleanup__(f)))&lt;/code&gt;&lt;/a&gt;&#xA;language extension. This extension lets you attach a cleanup function&#xA;to a variable scope, but the cleanup is defined in the place where the&#xA;variable is defined, not in the type.&lt;/p&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;As readers pointed out, a newer variant of C#&amp;rsquo;s &lt;code&gt;using&lt;/code&gt; statement&#xA;implements a similar non-nested cleanup bound to variable lifetime as&#xA;well. See &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement&#34;&gt;the&#xA;documentation&lt;/a&gt;&#xA;comment by Morgens Heller Gabe below for examples.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;defer-statement-go&#34;&gt;&amp;ldquo;Defer&amp;rdquo; statement (Go)&lt;/h2&gt;&#xA;&lt;p&gt;Go has a language feature for cleanup:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func (m *MyClass) Frobnicate() {&#xA;  m.mu_.Lock()&#xA;  defer m.mu_.Unlock()&#xA;&#xA;  Foo()  // under lock&#xA;  Bar()  // under lock&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The &lt;code&gt;defer&lt;/code&gt; statement defers a function call until the current&#xA;function exits.&lt;/p&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;The Zig language has syntactically similar&#xA;&lt;a href=&#34;https://ziglang.org/documentation/master/#defer&#34;&gt;&lt;code&gt;defer&lt;/code&gt;&lt;/a&gt; and&#xA;&lt;a href=&#34;https://ziglang.org/documentation/master/#errdefer&#34;&gt;&lt;code&gt;errdefer&lt;/code&gt;&lt;/a&gt;&#xA;statements. Other than in Go, Zig executes the cleanup when leaving&#xA;the current scope rather than when leaving the function.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;with-blocks-and-friends-python-c-java&#34;&gt;&amp;ldquo;With&amp;rdquo; blocks and friends (Python, C#, Java)&lt;/h2&gt;&#xA;&lt;p&gt;Python implements a syntax which makes it easy to acquire resources&#xA;during the execution of a nested block of commands.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def readfullfile(filename):&#xA;  with open(filename) as f:&#xA;    print(&amp;quot;opened&amp;quot;, filename)&#xA;    return f.read()&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The object returned by &lt;code&gt;open()&lt;/code&gt; implements the &lt;a href=&#34;https://docs.python.org/3/reference/datamodel.html#context-managers&#34;&gt;context&#xA;manager&lt;/a&gt;&#xA;protocol, which means that it can be used in a &lt;code&gt;with&lt;/code&gt; statement. When&#xA;the &lt;code&gt;with&lt;/code&gt;-scope exits, the cleanup functions will automatically get&#xA;invoked (e.g. to close a file).&lt;/p&gt;&#xA;&lt;p&gt;Cleanups get invoked in all cases when execution leaves the scope, no&#xA;matter whether it happens through a regular exit, an early return or&#xA;an exception.&lt;/p&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; Multiple readers have pointed out that both C# and Java&#xA;now support equivalent scoped statements as well:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;In C# with the &lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/statements#the-using-statement&#34;&gt;&lt;code&gt;using&lt;/code&gt;&#xA;statement&lt;/a&gt;&#xA;(see&#xA;&lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement&#34;&gt;documentation&lt;/a&gt;&#xA;and Mogens Heller Grabe&amp;rsquo;s comment with examples below).&lt;/li&gt;&#xA;&lt;li&gt;in Java with the&#xA;&lt;a href=&#34;https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.20.3.1&#34;&gt;try-with-resources&lt;/a&gt;&#xA;syntax. (&lt;a href=&#34;https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html&#34;&gt;examples&lt;/a&gt;)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In all of these cases, the used object must implement a special&#xA;interface with a cleanup method, such as &lt;code&gt;IDisposable&lt;/code&gt; in C# and&#xA;&lt;code&gt;AutoCloseable&lt;/code&gt; in Java.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;macros-lisp&#34;&gt;Macros (Lisp)&lt;/h2&gt;&#xA;&lt;p&gt;Lisp macros are used similarly as Python&amp;rsquo;s &lt;code&gt;with&lt;/code&gt; statements, but&#xA;offer more ways to shoot yourself in the foot (Example from the book&#xA;&lt;a href=&#34;http://www.gigamonkeys.com/book/files-and-file-io.html#closing-files&#34;&gt;Practical Common&#xA;Lisp&lt;/a&gt;&#xA;by Peter Seibel):&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-lisp&#34;&gt;(with-open-file (stream &amp;quot;/some/file/name.txt&amp;quot; :direction :output)&#xA;  (format stream &amp;quot;Some text.&amp;quot;))  ; while file is opened&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Here, &lt;code&gt;with-open-file&lt;/code&gt; is a macro caring about opening and closing&#xA;(&lt;a href=&#34;http://clhs.lisp.se/Body/m_w_open.htm&#34;&gt;spec&lt;/a&gt;). The user of the macro&#xA;provides a sequence of commands to be executed with the opened file&#xA;stream, e.g. &lt;code&gt;(format stream &amp;quot;Some text.&amp;quot;)&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;These macros are often named to start with the word &lt;code&gt;with&lt;/code&gt;, but there&#xA;are exceptions, e.g. &lt;a href=&#34;https://www.gnu.org/software/emacs/manual/html_node/eintr/save_002dexcursion.html&#34;&gt;&lt;code&gt;save-excursion&lt;/code&gt;&lt;/a&gt; in Emacs Lisp.&lt;/p&gt;&#xA;&lt;p&gt;From the caller perspective, this behaves similar as Python&amp;rsquo;s &lt;code&gt;with&lt;/code&gt;&#xA;statements, but they are usually expanded into a use of&#xA;&lt;a href=&#34;http://clhs.lisp.se/Body/s_unwind.htm&#34;&gt;&lt;code&gt;unwind-protect&lt;/code&gt;&lt;/a&gt; or a similar&#xA;primitive depending on the Lisp dialect (comparable to&#xA;&lt;code&gt;try&lt;/code&gt;&amp;hellip;&lt;code&gt;finally&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;h2 id=&#34;try-finally-blocks-java-c-python-&#34;&gt;Try-Finally blocks (Java, C#, Python, &amp;hellip;)&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;try&lt;/code&gt; blocks are primarily used for catching exceptions but in many&#xA;languages they offer a &lt;code&gt;finally&lt;/code&gt; clause as well, which is guaranteed&#xA;to execute whenever the &lt;code&gt;try&lt;/code&gt; scope exits, including on early returns&#xA;and exceptions.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;setUp();&#xA;try {&#xA;  doWork();  // between setup and cleanup&#xA;} finally {&#xA;  cleanUp();&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The construct tends to lead to deeper nesting when multiple cleanups&#xA;need to be done.&lt;/p&gt;&#xA;&lt;p&gt;Some languages have &lt;code&gt;finally&lt;/code&gt;-like constructs which are independent of&#xA;exceptions, like Smalltalk with&#xA;&lt;a href=&#34;https://www.gnu.org/software/smalltalk/manual/html_node/Hooking-into-the-stack-unwinding.html#Hooking-into-the-stack-unwinding&#34;&gt;Block&amp;raquo;ensure:&lt;/a&gt;,&#xA;&lt;a href=&#34;http://clhs.lisp.se/Body/s_unwind.htm&#34;&gt;&lt;code&gt;unwind-protect&lt;/code&gt;&lt;/a&gt; in Common&#xA;Lisp (&lt;a href=&#34;https://wiki.c2.com/?UnwindProtect&#34;&gt;on C2 wiki&lt;/a&gt;) and&#xA;&lt;a href=&#34;https://www.scheme.com/tspl4/control.html#desc:dynamic-wind&#34;&gt;&lt;code&gt;dynamic-wind&lt;/code&gt;&lt;/a&gt;&#xA;in Scheme. (Scheme has the additional complication that it needs to&#xA;deal with continuations.)&lt;/p&gt;&#xA;&lt;h2 id=&#34;todo-lists-languages-with-closures&#34;&gt;Todo lists (languages with closures)&lt;/h2&gt;&#xA;&lt;p&gt;In languages with closures or higher order functions, one way to defer&#xA;work for later is to save functions to be executed later in a list:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def frobnicate():&#xA;  todos = []&#xA;&#xA;  setUp()&#xA;  todos.append(cleanUp)&#xA;&#xA;  doWork()  # between setup and cleanup&#xA;&#xA;  for f in todos:&#xA;    f()&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;One variant of this is&#xA;&lt;a href=&#34;https://docs.python.org/3/library/unittest.html#unittest.TestCase.addCleanup&#34;&gt;&lt;code&gt;addCleanup&lt;/code&gt;&lt;/a&gt;&#xA;in Python&amp;rsquo;s &lt;code&gt;unittest&lt;/code&gt; library for deferring work to be done after the&#xA;test execution. Note that similarly to &lt;code&gt;defer&lt;/code&gt; in Go, this lets us&#xA;place the cleanup right together with the setup code, so that we can&#xA;easily keep them in sync:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;class FooTest(unittest.TestCase):&#xA;  # ...&#xA;&#xA;  def test_service(self):&#xA;    srv = StartService()&#xA;    self.addCleanup(srv.Stop)&#xA;&#xA;    self.assertEqual(42, srv.Invoke())  # while running&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Since version 1.14, &lt;a href=&#34;/post/go-testing-cleanup/&#34;&gt;Go supports the&#xA;same&lt;/a&gt; with&#xA;&lt;a href=&#34;https://godoc.org/testing#T.Cleanup&#34;&gt;&lt;code&gt;T.Cleanup()&lt;/code&gt;&lt;/a&gt; to simplify test&#xA;cleanup.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;passing-higher-order-functions-ruby-lisp&#34;&gt;Passing higher-order functions (Ruby, Lisp?)&lt;/h2&gt;&#xA;&lt;p&gt;In Ruby, it&amp;rsquo;s common to pass down functions to other functions, as a&#xA;way of structuring control flow and also for cleanup purposes. This&#xA;invocation of&#xA;&lt;a href=&#34;https://ruby-doc.org/core-2.7.0/File.html#method-c-open&#34;&gt;&lt;code&gt;File.open&lt;/code&gt;&lt;/a&gt;&#xA;executes the passed function (in between &lt;code&gt;do&lt;/code&gt;&amp;hellip;&lt;code&gt;end&lt;/code&gt;) with the opened&#xA;file and closes the file afterwards.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ruby&#34;&gt;File.open(&amp;quot;output.txt&amp;quot;, &amp;quot;a&amp;quot;) do |f|&#xA;  f.puts(&amp;quot;Hello, world!&amp;quot;)  # while file is open&#xA;end&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This general technique is possible in all languages where passing down&#xA;higher order functions is cheap, such as Ruby and Smalltalk. Smalltalk&#xA;takes it to another level by expressing all control flow as method&#xA;invocations, including loops and &lt;code&gt;ifTrue:&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not sure to what extent the Smalltalks use higher order functions&#xA;for cleanup purposes as Ruby does. More imperative Lisps like CL&#xA;expose mostly macros to users, but these will in turn use&#xA;closure-passing unwinding primitives under the hood.&lt;/p&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;There is a technical twist here in that languages need special&#xA;guarantees for these so-called &lt;em&gt;downward funargs&lt;/em&gt; to be cheap.&lt;/p&gt;&#xA;&lt;p&gt;A &lt;em&gt;downward funarg&lt;/em&gt; is a higher-order function which is only passed&#xA;down the stack but does not outlive the stack frame in which it was&#xA;created. The captured variables referenced by a downward funarg can&#xA;happily stay on the stack. This is in contrast to upward funargs which&#xA;outlive their own stack frame and which require invisible heap&#xA;allocations or similar tricks in the language.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;setup-and-teardown-in-junit-xunit-&#34;&gt;setUp() and tearDown() in (JUnit, xUnit, &amp;hellip;)&lt;/h2&gt;&#xA;&lt;p&gt;In the &lt;em&gt;xUnit&lt;/em&gt; family of unit testing frameworks, tests are part of&#xA;classes which get executed by a test runner. Each test class may&#xA;optionally override &lt;code&gt;setUp()&lt;/code&gt; and &lt;code&gt;tearDown()&lt;/code&gt; hooks which get&#xA;executed around every test execution, independent of whether that test&#xA;succeeded or not.&lt;/p&gt;&#xA;&lt;p&gt;In languages which provide a sane way of cleanup, I think that&#xA;overriding the &lt;code&gt;tearDown()&lt;/code&gt; hook should not be necessary and probably&#xA;discouraged. I suspect the same is true for code in &lt;code&gt;setUp()&lt;/code&gt;, but&#xA;that is more related to ambient shared state which becomes tricky to&#xA;modify when multiple tests rely on a subset of it in implicit ways.&lt;/p&gt;&#xA;&lt;h2 id=&#34;discussion&#34;&gt;Discussion&lt;/h2&gt;&#xA;&lt;p&gt;These approaches are quite different to each other. In practice, the&#xA;programming language is often already fixed, and then only a limited&#xA;number of options exists.&lt;/p&gt;&#xA;&lt;p&gt;In general, I found that in practice it helps to have setup and&#xA;cleanup close to each other, so that it&amp;rsquo;s easy to keep them in sync.&#xA;Go&amp;rsquo;s &lt;code&gt;defer&lt;/code&gt; really shines there.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s even nicer to have them represented by the same construct as with&#xA;Python&amp;rsquo;s &lt;code&gt;with&lt;/code&gt; or C++&amp;rsquo;s RAII idiom. With these, the usage becomes&#xA;safer, but you trade that for more effort in implementing classes&#xA;that are &lt;code&gt;with&lt;/code&gt;- and RAII-enabled. (In Python, this is somewhat&#xA;mitigated with the&#xA;&lt;a href=&#34;https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager&#34;&gt;&lt;code&gt;contextlib&lt;/code&gt;&lt;/a&gt;&#xA;module.)&lt;/p&gt;&#xA;&lt;p&gt;Downward funargs are both safe, easy to use and simple to implement,&#xA;but have a little overhead. That&amp;rsquo;s fair in most scripting cases, but&#xA;wouldn&amp;rsquo;t work for the Linux kernel.&lt;/p&gt;&#xA;&lt;p&gt;In terms of reasoning about performance, the Linux kernel has the&#xA;approach where it&amp;rsquo;s most obvious what this translates to at the&#xA;machine level, but the same amount of control is usually not needed in&#xA;user space programs.&lt;/p&gt;&#xA;&lt;p&gt;At a higher level, I believe it pays off to care about symmetric setup&#xA;and cleanup steps as part of the same function wherever possible. It&#xA;makes it easier to track where setups and cleanups are done across the&#xA;codebase, and refactoring to such a design reduces state that has to&#xA;be tracked across multiple functions.&lt;/p&gt;&#xA;&lt;h2 id=&#34;update-2020-01-13&#34;&gt;Update (2020-01-13)&lt;/h2&gt;&#xA;&lt;p&gt;Thanks for all the positive feedback, particularly to everyone who&#xA;pointed out related language features:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;C#&amp;rsquo;s &lt;code&gt;using&lt;/code&gt;:&lt;/strong&gt; Thanks, Mogens Heller Grabe for the longer&#xA;explanation in the comments below, as well as the users&#xA;&lt;a href=&#34;https://news.ycombinator.com/item?id=21996141&#34;&gt;hateful&lt;/a&gt; and&#xA;&lt;a href=&#34;https://news.ycombinator.com/item?id=21996141&#34;&gt;niklasjansson&lt;/a&gt; on HN&#xA;who pointed out documentation.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Java try-with-resources:&lt;/strong&gt;&#xA;&lt;a href=&#34;https://news.ycombinator.com/item?id=21997841&#34;&gt;cpt1138&lt;/a&gt; and&#xA;&lt;a href=&#34;https://news.ycombinator.com/item?id=21997848&#34;&gt;quantified&lt;/a&gt; on HN&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Rust&amp;rsquo;s &lt;code&gt;Drop&lt;/code&gt; trait:&lt;/strong&gt; &lt;a href=&#34;https://news.ycombinator.com/item?id=21998455&#34;&gt;jupp0r&lt;/a&gt; on HN&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Zig&amp;rsquo;s &lt;code&gt;defer&lt;/code&gt; and &lt;code&gt;errdefer&lt;/code&gt;:&lt;/strong&gt;&#xA;&lt;a href=&#34;https://news.ycombinator.com/item?id=21996444&#34;&gt;apta&lt;/a&gt; on HN&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;For &lt;strong&gt;Haskell&lt;/strong&gt;, &lt;a href=&#34;https://news.ycombinator.com/item?id=21998455&#34;&gt;lgas&lt;/a&gt;&#xA;mentioned the &lt;a href=&#34;https://wiki.haskell.org/Bracket_pattern&#34;&gt;Bracket&#xA;pattern&lt;/a&gt;, and&#xA;&lt;a href=&#34;https://news.ycombinator.com/item?id=21998455&#34;&gt;dwohnitmok&lt;/a&gt; is&#xA;mentioning linear types. Superficially this looks similar to&#xA;&lt;code&gt;dynamic-unwind&lt;/code&gt; and friends in Scheme and Lisp (see above), but my&#xA;Haskell-fu is too weak to judge it. I suspect it would be hard to&#xA;compare programming idioms between Haskell&amp;rsquo;s lazy purely functional&#xA;evaluation and imperative programs.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/setup-cleanup/</guid>
      <pubDate>Wed, 08 Jan 2020 21:37:28 +0100</pubDate>
    </item>
    <item>
      <title>Mutation Testing works</title>
      <link>https://blog.gnoack.org/post/mutation-testing</link>
      <description>&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;Disclaimer: These are Google topics, but based on publicly available&#xA;resources. I&amp;rsquo;m writing this not because I get paid for it (I don&amp;rsquo;t),&#xA;but because I am truly excited about it, it&amp;rsquo;s now public, and I hope&#xA;this will find more wide-spread adoption.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Goran Petrović and Marko Ivanković have published a nice paper on the&#xA;&lt;a href=&#34;https://research.google/pubs/pub46584/&#34;&gt;&amp;ldquo;State of Mutation Testing at&#xA;Google&amp;rdquo;&lt;/a&gt; a while&#xA;ago&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m a fan of this, because &lt;a href=&#34;https://en.m.wikipedia.org/wiki/Mutation_testing&#34;&gt;Mutation&#xA;Testing&lt;/a&gt; spots real&#xA;test coverage issues which regular line-based coverage won&amp;rsquo;t&#xA;catch. The approach presented in the paper runs mutation testing in&#xA;the context of a code review, which narrows the surfaced issues to the&#xA;code you&amp;rsquo;re already working on, and keeps them actionable.&lt;/p&gt;&#xA;&lt;h2 id=&#34;example&#34;&gt;Example&lt;/h2&gt;&#xA;&lt;p&gt;To get an idea how the approach in the paper works in practice: Let&amp;rsquo;s&#xA;assume we send a code review introducing a line such as &lt;code&gt;if (a == b || b == 1) {&lt;/code&gt;, but we didn&amp;rsquo;t bother testing the new code properly. With&#xA;mutation testing enabled, the infrastructure would now catch us and&#xA;report something like this (from page 2 in the paper):&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;Changing this 1 line to&#xA;&#xA;  if (a != b || b == 1) {&#xA;&#xA;does not cause any test exercising them to fail.&#xA;&#xA;Consider adding test cases that fail when the code is mutated to&#xA;ensure those bugs would be caught.&#xA;&#xA;Mutants ran because goranpetrovic is whitelisted.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;h2 id=&#34;how-did-it-figure-that-out&#34;&gt;How did it figure that out?&lt;/h2&gt;&#xA;&lt;p&gt;Mutation Testing runs on a proposed change during a code review, so&#xA;the problem is defined (a) through a bunch of changes to production&#xA;code and (b) implicitly through the tests exercising that code.&lt;/p&gt;&#xA;&lt;p&gt;The mutation testing infrastructure now attempts the following:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Purposefully modify program logic&lt;/strong&gt; in the code under test&#xA;(e.g. negate an &lt;code&gt;if&lt;/code&gt; condition)&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Check if any of the tests starts failing&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;We messed up the code under test, so with good &amp;ldquo;logical coverage&amp;rdquo;, one&#xA;of the tests should now have failed.  But if all tests have still&#xA;passed, we apparently have changed logic that wasn&amp;rsquo;t properly tested.&lt;/p&gt;&#xA;&lt;h2 id=&#34;mutation-testing-is-underrated&#34;&gt;Mutation Testing is underrated&lt;/h2&gt;&#xA;&lt;p&gt;Even though it looks like it&amp;rsquo;s originally coming from a more academic&#xA;corner, &lt;strong&gt;Mutation Testing works in practice.&lt;/strong&gt; The examples in the&#xA;paper are not made up, and they saved me from real bugs.&lt;/p&gt;&#xA;&lt;p&gt;I hope that there will be more implementations in the wild at some&#xA;point. It&amp;rsquo;s worth the effort.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Google started publishing more material on&#xA;internal developer tooling in the recent years. I ♥ it because I&#xA;can now point to papers rather than to Google PR when people ask&#xA;about it. :)&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;The list of implemented mutations is at the top of page&#xA;4 in the paper.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/mutation-testing/</guid>
      <pubDate>Mon, 30 Dec 2019 23:53:19 +0100</pubDate>
    </item>
    <item>
      <title>Wireguard is in the Linux kernel</title>
      <link>https://blog.gnoack.org/post/wireguard</link>
      <description>&lt;p&gt;I&amp;rsquo;m very happy to hear that &lt;a href=&#34;https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=e7096c131e5161fa3b8e52a650d7719d2857adfd&#34;&gt;Wireguard has made it into the Linux&#xA;kernel&lt;/a&gt;&#xA;and &lt;a href=&#34;https://lists.zx2c4.com/pipermail/wireguard/2019-December/004704.html&#34;&gt;will be part of Linux&#xA;5.6&lt;/a&gt;!&lt;/p&gt;&#xA;&lt;p&gt;Wireguard was quite a relief for me after wrestling with OpenVPN&#xA;before, for multiple reasons. I&amp;rsquo;m a happy user for about a year now,&#xA;and I can wholeheartedly recommend it.&lt;/p&gt;&#xA;&lt;p&gt;Wireguard is really pushing the state of the art forward for VPNs:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;high performance&lt;/strong&gt; (it&amp;rsquo;s part of the kernel)&lt;/li&gt;&#xA;&lt;li&gt;very &lt;strong&gt;simple setup&lt;/strong&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;no wrestling with OpenSSL or X.509 certificates&lt;/li&gt;&#xA;&lt;li&gt;key management&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; comparable to OpenSSH, &lt;strong&gt;keys fit one line&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;excellent apps&lt;/strong&gt; for&#xA;&lt;a href=&#34;https://play.google.com/store/apps/details?id=com.wireguard.android&#34;&gt;Android&lt;/a&gt;&#xA;and &lt;a href=&#34;https://apps.apple.com/de/app/wireguard/id1441195209&#34;&gt;iOS&lt;/a&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;full config files can be provided as QR codes&lt;/li&gt;&#xA;&lt;li&gt;it &lt;strong&gt;does not drain&lt;/strong&gt; your battery (a mostly stateless protocol)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.wireguard.com/install/&#34;&gt;support for many platforms&lt;/a&gt;&#xA;apart from Linux through a user space implementation&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;an-example-configuration&#34;&gt;An example configuration&lt;/h2&gt;&#xA;&lt;p&gt;To get an idea, this is a &lt;code&gt;/etc/wireguard/wg0.conf&lt;/code&gt; configuration file&#xA;very similar&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; to the one I use on my Laptop right now:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-ini&#34;&gt;[Interface]&#xA;PrivateKey = WG8r5DNvD2KlZORhJ2XgzW3lWO8i5GJqZBePt98EgUY=&#xA;Address = 192.168.23.10/32&#xA;DNS = 192.168.23.1&#xA;&#xA;[Peer]&#xA;PublicKey = 6qzH9hJbyPFp+GJJoxsBaPhUEl4mVKTGNP433xLWhBc=&#xA;PresharedKey = LiWmdHZN/Jizhv1h0qTGeslci2yZIyrkEDjrx3bUomE=&#xA;AllowedIPs = 0.0.0.0/0, ::/0&#xA;Endpoint = vpn.gnoack.org:9999&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This contains our own device&amp;rsquo;s private key and configured IP in the&#xA;VPN, as well as a list of peer hosts with their public keys and the IP&#xA;addresses they are making available.&lt;/p&gt;&#xA;&lt;p&gt;Note the symmetry: A server-side configuration looks the&#xA;same, but with more peer entries.&lt;/p&gt;&#xA;&lt;p&gt;The command &lt;code&gt;wg-quick up wg0&lt;/code&gt; brings the configuration up as a new device and sets&#xA;appropriate routes.&lt;/p&gt;&#xA;&lt;p&gt;For further reference, &lt;a href=&#34;https://www.wireguard.com/quickstart/&#34;&gt;Wireguard&amp;rsquo;s own quickstart&#xA;page&lt;/a&gt; has a better introduction&#xA;than this one here (with a video).&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I&amp;rsquo;m not affiliated with Wireguard, but I received a&#xA;big stack of stickers&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; from Jason Donenfeld after a talk&#xA;once. Congratulations on the big step forward, and thanks for the&#xA;great software!&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Key management can hardly be simpler than that:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ wg genkey &amp;gt; beuys.gnoack.org&#xA;$ wg pubkey &amp;lt; beuys.gnoack.org&#xA;tw6MlpAFMoQInDC402FndO8Z49/H4cT11BYOHDRkcys=&#xA;$ wg genpsk &amp;gt; psk&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This is a breeze compared to the OpenSSL dance required to get OpenVPN running.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;These are example values, of course.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;That was really nice. As everyone knows, stickers beat&#xA;Bitcoin and Ethereum hands down as an underground hacker&#xA;currency. :) They were quite popular in the sticker exchange where&#xA;I placed them. 🐉&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/wireguard/</guid>
      <pubDate>Mon, 09 Dec 2019 22:59:00 +0200</pubDate>
    </item>
    <item>
      <title>Go: Goroutines and Apis</title>
      <link>https://blog.gnoack.org/post/go-goroutines-and-apis</link>
      <description>&lt;p&gt;Goroutines are a unusual and powerful programming language feature, so&#xA;they are a tempting toy to play with, and they get a bit overused.&lt;/p&gt;&#xA;&lt;p&gt;There is some indication that the following Go principle holds true:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Strive to provide synchronous APIs,&lt;br&gt;&#xA;let the caller start goroutines.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;To put this advice into a more concrete code example:&lt;/p&gt;&#xA;&lt;p&gt;Do this:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-golang&#34;&gt;func (t *type) Run(ctx context.Context) {&#xA;    // Implementation of background task&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Instead of this:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-golang&#34;&gt;func (t *type) Start() {&#xA;    t.wg.Add(1)&#xA;    go func() {&#xA;      // Implementation of background task&#xA;      t.wg.Done()&#xA;    }&#xA;}&#xA;&#xA;func (t *type) Cancel() {&#xA;    // Somehow cancel the background task&#xA;    t.wg.Wait()&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Upsides for &lt;code&gt;Run()&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The caller gets to decide how the &amp;ldquo;background&amp;rdquo; code gets run.&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Spawning Goroutines is easy for the caller too.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;go&lt;/code&gt; statements move towards the application&amp;rsquo;s top-level.&#xA;Goroutine coordination becomes simpler to reason about when&#xA;goroutines are started in fewer places.&lt;/li&gt;&#xA;&lt;li&gt;Callers can also run it without goroutine and just block on the&#xA;call, if there is no other work to be done.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Background task cancellation is provided through the context.&lt;/li&gt;&#xA;&lt;li&gt;Waiting for the background task has a trivial API (when the&#xA;function returns), and can be done with the mechanism the&#xA;caller prefers (waitgroups, channels, &amp;hellip;)&lt;/li&gt;&#xA;&lt;li&gt;It&amp;rsquo;s on the safe side API-wise: There is no &lt;code&gt;Close()&lt;/code&gt; method which&#xA;callers can forget to call (leaking resources).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Another great discussion of this was in the &lt;a href=&#34;https://changelog.com/gotime/102#transcript-91&#34;&gt;Go Time: On application&#xA;design&lt;/a&gt; podcast&#xA;(starting around minute 47, Mat Ryer&amp;rsquo;s explanation really resonated&#xA;with me)&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Addendum&lt;/strong&gt;: In the comments, Jacob is pointing out the talk &lt;a href=&#34;https://www.youtube.com/watch?v=LHe1Cb_Ud_M&#34;&gt;&amp;ldquo;Ways&#xA;To Do Things&amp;rdquo; by Peter&#xA;Bourgon&lt;/a&gt;&#xA;&lt;a href=&#34;https://speakerdeck.com/peterbourgon/ways-to-do-things?slide=14&#34;&gt;(slides)&lt;/a&gt;,&#xA;who also appeared on the above &amp;ldquo;Go Time&amp;rdquo; episode. The talk describes&#xA;the &lt;code&gt;Run()&lt;/code&gt; style quite clearly and in more detail. Thanks for the&#xA;excellent pointer, Jacob!&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/go-goroutines-and-apis/</guid>
      <pubDate>Mon, 18 Nov 2019 21:30:53 +0100</pubDate>
    </item>
    <item>
      <title>Go: Error handling</title>
      <link>https://blog.gnoack.org/post/go-errors</link>
      <description>&lt;img src=&#34;/images/rakegopher.png&#34;&gt;&#xA;&lt;p&gt;I love Go for many reasons, but this part is still itching me: I&#xA;postulate that this Go idiom is a burden on our mental capacity:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-golang&#34;&gt;if err != nil {&#xA;        return nil&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;a href=&#34;/post/error_handling&#34;&gt;Error handling&lt;/a&gt; is at a tension between two&#xA;different developer needs.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;On the one hand, error handling is very annoying and distracting&#xA;when working on a constructively formulated use case.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;On the other hand, not dealing with it correctly means that the&#xA;program blows up in unknown ways when errors happen: data may be lost,&#xA;I/O may be half done, features may stop working without the developers&#xA;noticing.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Go&amp;rsquo;s explicitness about error handling makes sure that errors are not&#xA;falling through the cracks as easily as they might do in other&#xA;languages where errors are implicitly propagated&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;. While many&#xA;developers love Go&amp;rsquo;s explicitness, it is rather verbose for error&#xA;handling, which has drawbacks:&lt;/p&gt;&#xA;&lt;p&gt;(1) &lt;strong&gt;Humans are pattern matching machines&lt;/strong&gt;. Whenever we see text&#xA;whose shape resembles the error checking idiom, we have trained&#xA;ourselves to quickly look away, at the more important code next to it.&#xA;But bugs can hide even in repetitive parts of the code. If we&amp;rsquo;re not&#xA;looking at the idiom anyway, the &lt;code&gt;try()&lt;/code&gt; proposal might be the better&#xA;option, as it de-duplicates the check.&lt;/p&gt;&#xA;&lt;p&gt;(2) &lt;strong&gt;The error handling pattern takes much visual space&lt;/strong&gt;. The idiom&#xA;is necessarily interleaved with the code on the happy path. In many&#xA;cases, the flow of reading a longer function is interrupted multiple&#xA;times with the same 3-line code snippet.&lt;/p&gt;&#xA;&lt;h2 id=&#34;two-different-hats&#34;&gt;Two different hats&lt;/h2&gt;&#xA;&lt;p&gt;I believe that reasoning about constructive code and reasoning about&#xA;error handling code are &lt;em&gt;two separate states of mind&lt;/em&gt;, and switching&#xA;back and forth between these is straining and tedious&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;p&gt;A consequence of this is: It might be more productive to spend less&#xA;time on errors during an initial implementation, and to then address&#xA;the error handling aspect in a separate pass over the code. It&#xA;would be better if we could have two views of the code for these&#xA;different modes of operation:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;For the feature-focused mode, we need a view of the code where the&#xA;error handling stays explicit&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, but goes out of the way visually in&#xA;order to make the constructive code more prominent.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;For the error-focused mode, an error-only view is not really&#xA;possible because errors happen only as a result of constructive&#xA;operations, but in many editors, an interactive search for the&#xA;keywords &lt;code&gt;return&lt;/code&gt; and &lt;code&gt;try&lt;/code&gt;(?) will highlight the possible exit points&#xA;quickly.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I think this would have been well solved with &lt;a href=&#34;https://github.com/golang/go/issues/32437&#34;&gt;Robert Griesemer&amp;rsquo;s&#xA;&lt;code&gt;try()&lt;/code&gt; proposal&lt;/a&gt;. I&amp;rsquo;m&#xA;still sad it didn&amp;rsquo;t go forward, but I&amp;rsquo;m glad the Go team is so careful&#xA;about it.&lt;/p&gt;&#xA;&lt;p&gt;Just because we have all already invested so much time into accepting&#xA;this verbose way of error handling, that doesn&amp;rsquo;t mean it&amp;rsquo;s a good&#xA;thing&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;This is particularly true for languages with exceptions where all function calls may implicitly throw exceptions and bubble them up the stack.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;It&amp;rsquo;s the same as trying to write a scientific article straight in LaTeX form: You can somehow do it, but the first formula will derail your train of thought so hard that the article will probably suffer from it. It&amp;rsquo;s a better option to write the article in a different medium and then do the translation to LaTeX in a separate step.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;Explicitness is a stated goal in Go, after all.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://en.wikipedia.org/wiki/Sunk_cost&#34;&gt;sunk cost effect&lt;/a&gt; is strong for programming languages! But then again, it&amp;rsquo;s much worse for C++. :)&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/go-errors/</guid>
      <pubDate>Fri, 18 Oct 2019 20:00:00 +0200</pubDate>
    </item>
    <item>
      <title>Dot Space Space</title>
      <link>https://blog.gnoack.org/post/dot-space-space</link>
      <description>&lt;p&gt;I have a confession to make. I used to put two spaces after each&#xA;sentence, as it used to be done by typewriter typists before computers&#xA;[citation needed].&lt;/p&gt;&#xA;&lt;p&gt;The moment when I started to form this habit was around 2010. I was&#xA;young and easy to impress, and I was picking up more advanced Emacs&#xA;use working on the TeX source for my diploma thesis. While editing&#xA;these large chunks of free-form text was when I discovered the&#xA;&lt;code&gt;fill-paragraph&lt;/code&gt; function, otherwise known as &lt;code&gt;M-q&lt;/code&gt;, for automatically&#xA;breaking text at the correct column width. This is very useful for&#xA;editing text files, so it quickly became a habit to press &lt;code&gt;M-q&lt;/code&gt; often.&#xA;I&amp;rsquo;m still using it right now, in fact.&lt;/p&gt;&#xA;&lt;p&gt;Now after a while of using this feature, I started to observe that&#xA;this didn&amp;rsquo;t work exactly how I thought. Readjusting text where one&#xA;line ends with a full stop, it would surprisingly add two spaces in&#xA;the middle after breaking.&lt;/p&gt;&#xA;&lt;p&gt;This text:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;Hello world.&#xA;This is a text.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Would turn into that text after a press of &lt;code&gt;M-q&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;Hello world.  This is a text.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then somewhere in Emacs documentation, I found key bindings jump back&#xA;and forth between sentences (&lt;code&gt;M-a&lt;/code&gt;, &lt;code&gt;M-e&lt;/code&gt;), and these also rely on&#xA;double-spaces to tell apart sentence ends from abbreviations (e.g. as&#xA;in Dr. Seuss).&lt;/p&gt;&#xA;&lt;p&gt;I did not end up picking up these shortcuts, but it was enough for me&#xA;to rationalize the double spaces. In reality of course, I only started&#xA;with that habit so that &lt;code&gt;M-q&lt;/code&gt; would not break the otherwise consistent&#xA;spacing style accidentally.&lt;/p&gt;&#xA;&lt;p&gt;I used this for about 10 years now, and of course it didn&amp;rsquo;t elude me&#xA;that I was the only one doing that, but it&amp;rsquo;s hard to put down old&#xA;habits.&lt;/p&gt;&#xA;&lt;p&gt;Recently, I found what I think is the remedy - &lt;code&gt;M-q&lt;/code&gt; is actually&#xA;configurable in Emacs, and the old habit of pressing &lt;code&gt;M-q&lt;/code&gt; will now&#xA;just fix my sentences to single-spaces again going forward.&lt;/p&gt;&#xA;&lt;p&gt;This is what is needed:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-elisp&#34;&gt;(setq sentence-end-double-space nil)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;What do we learn from this story? I&amp;rsquo;m not sure. Maybe that old habits&#xA;are hard to shake. Some quirks creep in without you noticing and a&#xA;tiny change might fix them.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;M-q&lt;/code&gt;&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/dot-space-space/</guid>
      <pubDate>Fri, 30 Aug 2019 09:17:30 +0200</pubDate>
    </item>
    <item>
      <title>Notes on Plan9&#39;s 9P protocol</title>
      <link>https://blog.gnoack.org/post/9pnotes</link>
      <description>&lt;p&gt;The 9P protocol is the protocol for network file systems used in&#xA;Plan9.  Plan9 is not a widely used operating system, but it&amp;rsquo;s widely&#xA;considered more true to the Unix spirit than Unix is, coming from some&#xA;of the same people who made Unix as well.&lt;/p&gt;&#xA;&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;&#xA;&lt;p&gt;To get a rough idea of the 9P protocol, consider a system where all&#xA;the original core Unix syscalls (&lt;code&gt;open&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;,&#xA;&amp;hellip;)  are lifted to be remote procedure calls.  These can be sent over&#xA;the network, making file systems in Plan9 network transparent.&lt;/p&gt;&#xA;&lt;p&gt;9P&amp;rsquo;s RPCs are documented in Plan9&amp;rsquo;s man page section 5&#xA;(&lt;a href=&#34;http://man.cat-v.org/plan_9/5/&#34;&gt;mirror&lt;/a&gt;). The supported methods are:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Session control&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;version&lt;/code&gt; to negotiate the protocol version.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;attach&lt;/code&gt; and &lt;code&gt;auth&lt;/code&gt; for authenticating with the remote end and opening a session.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;flush&lt;/code&gt; to cancel ongoing requests.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Basic file operations:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;open&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;clunk&lt;/code&gt; (equivalent to &lt;code&gt;close&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;read&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;write&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Working with directories&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;walk&lt;/code&gt; to descend a directory hierarchy.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;stat&lt;/code&gt; and &lt;code&gt;wstat&lt;/code&gt; for querying and modifying a directory entries&#xA;(e.g. to rename files).&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;remove&lt;/code&gt; for removing file entries.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;In addition, there is an &lt;code&gt;error&lt;/code&gt; response which may be returned as&#xA;response to any of these requests.&lt;/p&gt;&#xA;&lt;p&gt;The handle to a file is called &amp;ldquo;file ID&amp;rdquo; (fid) in Plan9 lingo.  This&#xA;can be thought of as a file descriptor.&lt;/p&gt;&#xA;&lt;p&gt;The 9P protocol does away with a bunch of complexities that Unix, and&#xA;particularly Linux has started to have.  Some of the non-supported&#xA;features are:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;There is no &lt;code&gt;mmap&lt;/code&gt; support in the protocol.  This can&amp;rsquo;t be done over the network.&#xA;Presumably it can be emulated within limits if needed.&lt;/li&gt;&#xA;&lt;li&gt;No &lt;code&gt;ioctl&lt;/code&gt;.  Control-style operations are often done through reads and writes&#xA;to special purpose files.  There are often textual command languages.&lt;/li&gt;&#xA;&lt;li&gt;There is a more blurry line between different file types as in Unix,&#xA;where you can distinguish named pipes, device files and regular files.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;playing-with-plan9port&#34;&gt;Playing with Plan9port&lt;/h2&gt;&#xA;&lt;p&gt;An easy way to play with 9P is to install Plan9port, a Linux userspace&#xA;port of Plan9&amp;rsquo;s core tools and wiring, which comes as installable&#xA;package with many Linux distributions.&lt;/p&gt;&#xA;&lt;p&gt;Plan9port includes the Plan9 compiler, the acme text editor and other&#xA;things, including source code.  Many of these tools are exposing 9P&#xA;file systems over Unix sockets in the &lt;code&gt;/tmp/ns.$USER.$DISPLAY&lt;/code&gt;&#xA;directory.  You can get your exact location by running the &lt;code&gt;namespace&lt;/code&gt;&#xA;tool:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ namespace&#xA;/tmp/ns.me:0&#xA;$&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;For example, you might want to start up the &lt;code&gt;factotum&lt;/code&gt; authentication&#xA;service and have a peek at the created file system.  Plan9port&amp;rsquo;s &lt;code&gt;9p&lt;/code&gt;&#xA;tool lets you do simple accesses from the command line:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ /usr/lib/plan9/bin/factotum&#xA;$ ls $(namespace)&#xA;factotum&#xA;$ 9p ls factotum&#xA;confirm&#xA;conv&#xA;ctl&#xA;log&#xA;needkey&#xA;proto&#xA;rpc&#xA;$&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;There are also public 9P services, such as the Plan9 &amp;ldquo;sources&amp;rdquo;&#xA;repository.  After the Bell Labs server went down, these continue to&#xA;be mirrored by services such as &lt;code&gt;sources.9p.io&lt;/code&gt;.  The &lt;code&gt;9fs&lt;/code&gt; script is&#xA;a wrapper for quickly mounting this remote service, but needs a little&#xA;modification for convenience:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ cat $PLAN9/bin/9fs \&#xA;  | sed -e &#39;s/sources.cs.bell-labs.com/sources.9p.io/&#39; &amp;gt; ~/9fs&#xA;$ chmod +x ~/9fs&#xA;$ ~/9fs sources&#xA;$ 9p ls -l sources&#xA;d-rwxrwxr-x M 0 9grid  9grid       0 Jul 15  2018 9grid&#xA;--rw-rw-r-- M 0 bootes sys         0 Jun 24  2013 _sources&#xA;d-rwxrwxr-x M 0 adm    sys         0 Jul 15  2018 adm&#xA;d-rwxrwxr-x M 0 sys    sys         0 Oct 28 19:18 contrib&#xA;d-rwxrwxr-x M 0 bootes sys         0 Jul 15  2018 dist&#xA;d-rwxrwxr-x M 0 sys    sys         0 Jul 15  2018 extra&#xA;--rw-rw-r-- M 0 bootes adm   9142602 Jan 23  2015 lsr&#xA;d-rwxrwxr-x M 0 geoff  nix         0 Jul 15  2018 nix&#xA;d-rwxrwxrwx M 0 glenda sys         0 Jul 15  2018 patch&#xA;d-rwxrwxr-x M 0 sys    sys         0 Jul 15  2018 plan9&#xA;d-rwxrwxr-x M 0 sys    sys         0 Jul 15  2018 wiki&#xA;d-rwxrwxr-x M 0 xen    xen         0 Jul 15  2018 xen&#xA;$&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;&lt;strong&gt;💡 Hint&lt;/strong&gt;: To make browsing more convenient, you may also use the FUSE&#xA;file system which comes with Plan9port:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ 9pfuse unix\!/tmp/ns.$USER.:0/sources /tmp/sources&#xA;$ ls /tmp/sources&#xA;9grid  contrib  extra  nix    plan9     wiki&#xA;adm    dist     lsr    patch  _sources  xen&#xA;$&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;looking-under-the-hood&#34;&gt;Looking under the hood&lt;/h2&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s first read a small file from the Plan9 sources repo:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ 9p read sources/plan9/NOTICE&#xA;Copyright © 2002 Lucent Technologies Inc.&#xA;All Rights Reserved&#xA;$&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;To look under the hood of 9P, we can use the &lt;code&gt;9p&lt;/code&gt; tool&amp;rsquo;s &lt;code&gt;-D&lt;/code&gt; option&#xA;to print all requests and responses.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;$ 9p -D read sources/plan9/NOTICE&#xA;&amp;lt;- Tversion tag 0 msize 8192 version &#39;9P2000&#39;&#xA;-&amp;gt; Rversion tag 65535 msize 8192 version &#39;9P2000&#39;&#xA;&amp;lt;- Tauth tag 0 afid 0 uname me aname &amp;lt;nil&amp;gt;&#xA;-&amp;gt; Rerror tag 0 ename authentication rejected&#xA;&amp;lt;- Tattach tag 0 fid 0 afid -1 uname me aname&#xA;-&amp;gt; Rattach tag 0 qid (0000000000000002 0 d)&#xA;&amp;lt;- Twalk tag 0 fid 0 newfid 1 nwname 2 0:plan9 1:NOTICE&#xA;-&amp;gt; Rwalk tag 0 nwqid 2 0:(0000000000081aa0 78 d) 1:(0000000000081d2d 1 )&#xA;&amp;lt;- Topen tag 0 fid 1 mode 0&#xA;-&amp;gt; Ropen tag 0 qid (0000000000081d2d 1 ) iounit 8168&#xA;&amp;lt;- Tread tag 0 fid 1 offset 0 count 4096&#xA;-&amp;gt; Rread tag 0 count 63 &#39;436f7079 72696768 7420c2a9 20323030 32204c75 ...&#39;&#xA;Copyright © 2002 Lucent Technologies Inc.&#xA;All Rights Reserved&#xA;&amp;lt;- Tread tag 0 fid 1 offset 63 count 4096&#xA;-&amp;gt; Rread tag 0 count 0 &#39;&#39;&#xA;&amp;lt;- Tclunk tag 0 fid 1&#xA;-&amp;gt; Rclunk tag 0&#xA;$&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The &lt;code&gt;9p&lt;/code&gt; tool opens the Unix socket at &lt;code&gt;/tmp/ns.me.:0/sources&lt;/code&gt; to talk&#xA;to the backend service.&lt;/p&gt;&#xA;&lt;p&gt;The steps are:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Version negotiation: Both sides confirm the 9P variant they want&#xA;to speak.&lt;/li&gt;&#xA;&lt;li&gt;Authentication: The &lt;code&gt;auth&lt;/code&gt; call is normally supposed to return an&#xA;open file ID, over which to speak an authentication protocol. In&#xA;this case, the service permits unauthenticated read accesses, so&#xA;this is returning an error.&lt;/li&gt;&#xA;&lt;li&gt;Attaching to the service, retrieving the desired root directory,&#xA;in this case it&amp;rsquo;s just the root.&#xA;(Note, the client picks which FID it would like to use for&#xA;identifying files.)&lt;/li&gt;&#xA;&lt;li&gt;Walking down to the desired file from the root FID 0, yielding&#xA;the new FID 1.&lt;/li&gt;&#xA;&lt;li&gt;Opening the FID for reading.&lt;/li&gt;&#xA;&lt;li&gt;Calling &lt;code&gt;read&lt;/code&gt; until nothing is returned any more.&lt;/li&gt;&#xA;&lt;li&gt;Closing the FID with &lt;code&gt;clunk&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;some-resources&#34;&gt;Some resources&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;http://9p.cat-v.org/documentation/&#34;&gt;9p.cat-v.org/documentation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;http://man.cat-v.org/plan_9/5/intro&#34;&gt;Plan9 intro(5)&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.aqwari.net/9p/&#34;&gt;Writing a 9P server form scratch&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/9pnotes/</guid>
      <pubDate>Fri, 22 Mar 2019 12:26:34 +0100</pubDate>
    </item>
    <item>
      <title>How to review code</title>
      <link>https://blog.gnoack.org/post/code_reviews</link>
      <description>&lt;figure&gt;&#xA;&lt;img src=&#34;/images/2011.09.18_code_reviews.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;A code review that doesn&amp;rsquo;t go right. (image by &lt;a href=&#34;http://www.bonkersworld.net&#34;&gt;Manu Cornet&lt;/a&gt;, CC BY-NC-ND 3.0&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve reviewed over 3000 code changes so far in my career, and gone&#xA;through about same amount of reviews as change author.  When reviewing&#xA;code, here is the golden rule I follow:&lt;/p&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;Any code change that&amp;rsquo;s a strict improvement should be approved.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Most incoming code reviews are both improvements in some aspects and&#xA;make things worse in other aspects.&lt;/p&gt;&#xA;&lt;h2 id=&#34;understand-the-change&#34;&gt;Understand the change&lt;/h2&gt;&#xA;&lt;p&gt;It&amp;rsquo;s tempting to glance over a change and only comment on small&#xA;technical details or style issues, but this will eventually lead to&#xA;worse code.  In order to give meaningful feedback, understand the&#xA;change to the extent where you could have authored it yourself.&lt;/p&gt;&#xA;&lt;h2 id=&#34;blocking-issues-to-look-out-for&#34;&gt;Blocking issues to look out for&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;New code is unclear.&lt;/strong&gt; Ask: &amp;ldquo;I do not understand this&amp;rdquo;.  Insist&#xA;that it&amp;rsquo;s clarified in the final submitted code rather than in a&#xA;review comment.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;New code is missing tests.&lt;/strong&gt; Insist.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;New code does not fit into surrounding code&lt;/strong&gt; in terms of style&#xA;or common libraries used.  Insist.  Link to a canonical style guide&#xA;whenever you can.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Pre-existing tests were changed willy-nilly until they run&#xA;green.&lt;/strong&gt; This often takes the form of only a few lines added to&#xA;every existing test case.  These drive-by fixes accumulate easily&#xA;over the course of some changes, until the core idea behind the&#xA;test cases is obstructed.  Insist on refactoring the tests.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;New code introduces duplication&lt;/strong&gt; with existing code.  Ask to&#xA;deduplicate.  (This is particularly often seen in tests.)&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;The change is increasing code complexity.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Is there a better solution you can suggest?&lt;/li&gt;&#xA;&lt;li&gt;Does the change&amp;rsquo;s benefit outweigh the additional complexity?&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;The change introduces dependencies in places where they don&amp;rsquo;t&#xA;belong.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;non-blocking-issues-to-look-out-for&#34;&gt;Non-blocking issues to look out for&lt;/h2&gt;&#xA;&lt;p&gt;It&amp;rsquo;s fine to ask for additional changes that are not strictly&#xA;required, but always make it clear that they are optional.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;&amp;ldquo;I would have done it differently.&amp;rdquo;&lt;/strong&gt; is a gut reaction almost&#xA;all reviewers have sometimes.  But it&amp;rsquo;s a shortcut heuristic the&#xA;mind takes which usually has a more solid technical background.&lt;/p&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;Learn the specific technical background to your gut&#xA;reactions, so you can give concrete advice.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;If you can&amp;rsquo;t find a specific technical problem, consider asking why&#xA;it was done in that particular way.  There are often be good&#xA;reasons which only need to be clarified a bit in the code.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Pre-existing issues.&lt;/strong&gt; Point out if pre-existing issues can be&#xA;fixed in the same change, but explicitly mark it as optional.  Even&#xA;when not done immediately, this discussion helps to form a common&#xA;understanding which pre-existing patterns in the code should be&#xA;avoided and kept.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;taking-technical-debt-a-game-of-trust&#34;&gt;Taking technical debt: A game of trust&lt;/h2&gt;&#xA;&lt;p&gt;Sometimes you will want to approve newly introduced issues and&#xA;postpone their fixes until later&#xA;(&lt;a href=&#34;https://en.m.wikipedia.org/wiki/Technical_debt&#34;&gt;Technical debt&lt;/a&gt;).&#xA;Insist that the author will fix these issues after the submission.&lt;/p&gt;&#xA;&lt;p&gt;In these cases, code reviews are a game of trust.  A strategy that&#xA;works for me is: Assume the best from people you don&amp;rsquo;t know yet, but&#xA;don&amp;rsquo;t let them pass a second time if they didn&amp;rsquo;t pay off the technical&#xA;debt.&lt;/p&gt;&#xA;&lt;p&gt;Good ways to track technical debt issues are (1) a good memory for&#xA;names, (2) a shared bug tracker and (3) &lt;code&gt;TODO&lt;/code&gt; markers in the code&#xA;(for smaller things).&lt;/p&gt;&#xA;&lt;h2 id=&#34;behaviors-to-avoid-as-a-reviewer&#34;&gt;Behaviors to avoid as a reviewer&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Do not sit on a code review.&lt;/strong&gt; Even if you can&amp;rsquo;t do the review,&#xA;explain immediately why you can&amp;rsquo;t do it (yet) and redirect to&#xA;someone better suited if possible.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Never insist that reviewees fix pre-existing issues.&lt;/strong&gt; Being code&#xA;reviewer is a position of power, but it&amp;rsquo;s not a free pass to&#xA;blackmail others into doing your work.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Don&amp;rsquo;t block legitimate changes for corporate politics reasons.&lt;/strong&gt;&#xA;This should go without saying.  Do not use code reviews to unjustly&#xA;influence project priorities.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;parting-words&#34;&gt;Parting words&lt;/h2&gt;&#xA;&lt;p&gt;I hope this collection of code reviewing tips will help someone to&#xA;have more constructive code reviews. Remember:&lt;/p&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;The reviewer&amp;rsquo;s job is not to stand in the way, but to enable&#xA;the code submitter to do their best work.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Feedback is always appreciated. :)&lt;/p&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;&lt;strong&gt;Update 2019-09-05:&lt;/strong&gt;&#xA;My colleague &lt;a href=&#34;https://twitter.com/mkanat&#34;&gt;Max Kanat-Alexander&lt;/a&gt; has&#xA;compiled and open sourced Google&amp;rsquo;s guidelines for code reviews at&#xA;&lt;a href=&#34;https://google.github.io/eng-practices/&#34;&gt;https://google.github.io/eng-practices/&lt;/a&gt;. It&amp;rsquo;s a longer document&#xA;compiled from the input of many engineers and has a lot of good&#xA;advice. I can wholeheartedly endorse this.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/code_reviews/</guid>
      <pubDate>Thu, 14 Mar 2019 13:00:06 +0200</pubDate>
    </item>
    <item>
      <title>Procedurally generated trees</title>
      <link>https://blog.gnoack.org/post/trees</link>
      <description>&lt;script src=&#34;https://blog.gnoack.org/js/paper-full.min.js&#34;&gt;&lt;/script&gt;&#xA;&lt;figure class=&#34;nomargin&#34;&gt;&#xA;&lt;script type=&#34;text/paperscript&#34; canvas=&#34;trees&#34;&gt;&#xA;var x = new Point(1, 0);&#xA;var y = new Point(0, 1);&#xA;function config(treeColor, leafColor) {&#xA;    return {&#xA;        treeColor: treeColor,&#xA;        leafColor: leafColor&#xA;    }&#xA;}&#xA;function leaf(pt, angle, length, cfg) {&#xA;  var r = length / 3;&#xA;  var tip = pt - y*length;&#xA;  var mid = pt - y*(length/2);&#xA;  var l = new Path([&#xA;      new Segment(pt, - x*r, + x*r),&#xA;      new Segment(tip, + x*(r/2), - x*(r/2)),&#xA;      new Segment(pt, - x*r, + x*r)&#xA;  ]);&#xA;  l.rotate(angle, pt);&#xA;  l.style = { fillColor: cfg.leafColor, closed: true };&#xA;}&#xA;function branchpart(pt, angle, width, length, cfg) {&#xA;    var wh = width/2;&#xA;    var rect = Path.Rectangle(&#xA;          pt - x*wh + y*wh,&#xA;          pt + x*wh - y*(length+wh));&#xA;    rect.style = { fillColor: cfg.treeColor };&#xA;    rect.rotate(angle, pt);&#xA;    var nextroot = (pt - y*length).rotate(angle, pt);&#xA;    return {&#xA;        rect: rect,&#xA;        next: nextroot,&#xA;    }&#xA;}&#xA;function branch(pt, angle, width, length, cfg) {&#xA;  var b = branchpart(pt, angle, width, length, cfg);&#xA;  if (width &gt; 3) {&#xA;      // Recurse&#xA;      var n = b.next;&#xA;      var area = width*width;&#xA;      var ffrac = Math.random(); // first branch fraction of area&#xA;      var sfrac = 1-ffrac;  // same for second branch&#xA;      if (ffrac &gt; 0.05) {&#xA;        var fangle = angle + (1-ffrac)*45;&#xA;        branch(n, fangle, Math.sqrt(area*ffrac), length*0.92, cfg);&#xA;      }&#xA;      if (sfrac &gt; 0.05) {&#xA;        var sangle = angle - (1-sfrac)*45;&#xA;        branch(n, sangle, Math.sqrt(area*sfrac), length*0.92, cfg);&#xA;      }&#xA;  } else {&#xA;    leaf(b.next, angle, 20, cfg);&#xA;  }&#xA;}&#xA;function tree(pt, cfg) {&#xA;  branch(pt, 0, 40, 35, cfg);&#xA;}&#xA;function threeTreeHorizontalScene() {&#xA;    var fullwidth = paper.view.size.width;&#xA;    var height = paper.view.size.height;&#xA;    var border = Math.round(fullwidth/12) + 50;&#xA;    var frame = Path.Rectangle(new Point(border, height-322), new Point(fullwidth-border, height-2));&#xA;    frame.style = {&#xA;        fillColor: &#39;white&#39;,&#xA;        strokeColor: &#39;black&#39;,&#xA;        strokeWidth: 2&#xA;    }&#xA;    var innerwidth = fullwidth - 2*border;&#xA;    if (innerwidth &gt; 320) {&#xA;        tree(new Point(border + Math.round(innerwidth*(1/3)), height-23), config(&#34;black&#34;, &#34;Orange&#34;));&#xA;        tree(new Point(border + Math.round(innerwidth*(2/3)), height-23), config(&#34;black&#34;, &#34;#ec5800&#34;));&#xA;    } else {&#xA;        tree(new Point(border + Math.round(innerwidth*(1/2)), height-23), config(&#34;black&#34;, &#34;#03992c&#34;));&#xA;    }&#xA;}&#xA;threeTreeHorizontalScene();&#xA;&lt;/script&gt;&#xA;&lt;canvas data-paper-resize=&#34;true&#34; id=&#34;trees&#34; style=&#34;width:120%; margin-left: -10%; margin-right: -10%; height: 400px;&#34;&gt;&lt;/canvas&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;These trees are procedurally regenerated on every page load.&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;h2 id=&#34;basic-algorithm&#34;&gt;Basic algorithm&lt;/h2&gt;&#xA;&lt;p&gt;At the heart of this algorithm, there is a recursive function which&#xA;draws a tree branch (as a rotated black rectangle) and its&#xA;sub-branches.  The function calls itself recursively for the two&#xA;sub-branches, using smaller branch sizes.  When the width is finally&#xA;too small to continue for the branch we&amp;rsquo;re looking at, we draw a leaf&#xA;and stop there.&lt;/p&gt;&#xA;&lt;p&gt;To randomize the tree shape, we&amp;rsquo;re picking two weights from 0 to 1 for&#xA;the two branches, which are adding up to 1.  The weights are used to&#xA;determine the sub-branch sizes and angles.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The two sub-branches should together have the same surface area as&#xA;the parent when you saw through them. The surface area is split&#xA;according to weight.&lt;/li&gt;&#xA;&lt;li&gt;Big sub-branches (with big weight) grow straight ahead, while small&#xA;ones grow more to the side, up to 45°.&lt;/li&gt;&#xA;&lt;li&gt;Very small sub-branches with too small weights are cut off and not&#xA;drawn anymore.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;&#xA;&lt;p&gt;The Javascript library I&amp;rsquo;m using is &lt;a href=&#34;http://paperjs.org&#34;&gt;Paperjs&lt;/a&gt;,&#xA;which I discovered through&#xA;&lt;a href=&#34;https://media.ccc.de/v/35c3-1-generative-art-with-paper-js&#34;&gt;the PaperJS talk at 35C3&lt;/a&gt; by &lt;a href=&#34;https://bleeptrack.de&#34;&gt;bleeptrack&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/trees/</guid>
      <pubDate>Wed, 23 Jan 2019 11:35:00 +0200</pubDate>
    </item>
    <item>
      <title>Internet of Things: Wifi Radio</title>
      <link>https://blog.gnoack.org/post/wifi_radio</link>
      <description>&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; This setup stopped working after a few firmware&#xA;updates; there were apparently some software updates after the&#xA;&lt;a href=&#34;https://www.heise.de/newsticker/meldung/Massenhafter-Ausfall-von-Internetradios-4417248.html&#34;&gt;catalog of internet radio stations broke down&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Maybe they also read my bug report about the broken encoding and found&#xA;my blog post. I didn&amp;rsquo;t investigate much; superficially it looked like&#xA;the radio is just ignoring the returned IP address if it&amp;rsquo;s a local&#xA;one.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Have you ever wondered what your Internet-connected devices are doing&#xA;behind your back?&lt;/p&gt;&#xA;&lt;p&gt;My internet radio had some problems with displaying special characters&#xA;in the list of radio stations. When I&#xA;&lt;a href=&#34;https://www.unix-ag.uni-kl.de/~guenther/tcpdump-fun.html&#34;&gt;checked the network traffic on Wireshark&lt;/a&gt;,&#xA;I found that the protocol for connecting back was unencrypted, so I&#xA;made it talk through a proxy which modifies the responses on the fly&#xA;to fix up wrong character encoding in the server responses.&lt;/p&gt;&#xA;&lt;p&gt;Basic setup:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The router giving out DHCP leases makes clients talk to its own local DNS service.&lt;/li&gt;&#xA;&lt;li&gt;The DNS service rewires the hostname for the radio API to the local Raspberry Pi&amp;rsquo;s IP&lt;/li&gt;&#xA;&lt;li&gt;The Raspberry Pi acts as a reverse HTTP proxy for the actual domain and modifies&#xA;requests along the way.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/diagram600.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;Reverse proxies are simple to write in Go: use&#xA;&lt;a href=&#34;https://golang.org/pkg/net/http/httputil/#NewSingleHostReverseProxy&#34;&gt;&lt;code&gt;httputil.NewSingleHostReverseProxy&lt;/code&gt;&lt;/a&gt;&#xA;and then set the &lt;code&gt;ReverseProxy.ModifyResponse&lt;/code&gt; property on the&#xA;returned object to an appropriate modification function such as:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func Modify(resp *http.Response) error {&#xA;        b, err := ioutil.ReadAll(resp.Body)&#xA;        resp.Body.Close()&#xA;        if err != nil {&#xA;                return err&#xA;        }&#xA;        b = ModifyBody(b)  // Actual replacements done here.&#xA;        resp.Body = ioutil.NopCloser(bytes.NewReader(b))&#xA;        resp.ContentLength = int64(len(b))&#xA;        resp.Header.Set(&amp;quot;Content-Length&amp;quot;, strconv.Itoa(len(b)))&#xA;        return nil&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;In my implementation, I&amp;rsquo;m replacing some wrongly encoded characters&#xA;with correct ones in the server response, and for demo purposes and&#xA;some silly entertainment, I&amp;rsquo;m changing the German word for Switzerland&#xA;to &amp;ldquo;Swizzle&amp;rdquo;:&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/radio600.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/wifi_radio/</guid>
      <pubDate>Wed, 09 Jan 2019 12:05:01 +0100</pubDate>
    </item>
    <item>
      <title>Shared base fixture</title>
      <link>https://blog.gnoack.org/post/base_fixture</link>
      <description>&lt;figure&gt;&#xA;&lt;div id=&#39;pikchr-0&#39; class=&#39;pikchr&#39;&gt;&#xA;&lt;div class=&#39;pikchr-svg&#39; style=&#39;max-width:537px&#39;&gt;&#xA;&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#34;0 0 537.726 286.526&#34;&gt;&#xA;&lt;polygon points=&#34;74.16,63.2636 62.64,67.5836 62.64,58.9436&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M2.16,63.2636L68.4,63.2636&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;38.16&#34; y=&#34;51.5636&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;make&lt;/text&gt;&#xA;&lt;path d=&#34;M74.16,91.6101L159.199,91.6101L159.199,34.9172L74.16,34.9172Z&#34;  style=&#34;fill:rgb(255,228,196);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;116.68&#34; y=&#34;53.1836&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;base&lt;/text&gt;&#xA;&lt;text x=&#34;116.68&#34; y=&#34;73.3436&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;case&lt;/text&gt;&#xA;&lt;polygon points=&#34;385.971,63.2636 374.451,67.5836 374.451,58.9436&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M159.199,63.2636L380.211,63.2636&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M385.971,91.6101L471.01,91.6101L471.01,34.9172L385.971,34.9172Z&#34;  style=&#34;fill:rgb(240,255,240);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;428.491&#34; y=&#34;63.2636&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;result&lt;/text&gt;&#xA;&lt;path d=&#34;M159.199,176.649L244.239,176.649L244.239,119.957L159.199,119.957Z&#34;  style=&#34;fill:rgb(255,228,196);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;201.719&#34; y=&#34;138.223&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;derived&lt;/text&gt;&#xA;&lt;text x=&#34;201.719&#34; y=&#34;158.383&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;case 1&lt;/text&gt;&#xA;&lt;path d=&#34;M159.199,261.689L244.239,261.689L244.239,204.996L159.199,204.996Z&#34;  style=&#34;fill:rgb(255,228,196);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;201.719&#34; y=&#34;223.262&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;derived&lt;/text&gt;&#xA;&lt;text x=&#34;201.719&#34; y=&#34;243.422&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;case 2&lt;/text&gt;&#xA;&lt;path d=&#34;M385.971,176.649L471.01,176.649L471.01,119.957L385.971,119.957Z&#34;  style=&#34;fill:rgb(240,255,240);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;428.491&#34; y=&#34;148.303&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;result&lt;/text&gt;&#xA;&lt;path d=&#34;M385.971,261.689L471.01,261.689L471.01,204.996L385.971,204.996Z&#34;  style=&#34;fill:rgb(240,255,240);stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;text x=&#34;428.491&#34; y=&#34;233.342&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;result&lt;/text&gt;&#xA;&lt;polygon points=&#34;385.971,148.303 374.451,152.623 374.451,143.983&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M244.239,148.303L380.211,148.303&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;385.971,233.342 374.451,237.662 374.451,229.022&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M244.239,233.342L380.211,233.342&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M116.68,91.6101L116.68,233.342&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;159.199,233.342 147.679,237.662 147.679,229.022&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M116.68,233.342L153.439,233.342&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;polygon points=&#34;159.199,148.303 147.679,152.623 147.679,143.983&#34; style=&#34;fill:rgb(0,0,0)&#34;/&gt;&#xA;&lt;path d=&#34;M116.68,148.303L153.439,148.303&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);&#34; /&gt;&#xA;&lt;path d=&#34;M266.916,12.24L266.916,284.366&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:7.2,7.2;&#34; /&gt;&#xA;&lt;path d=&#34;M363.294,12.24L363.294,284.366&#34;  style=&#34;fill:none;stroke-width:2.16;stroke:rgb(0,0,0);stroke-dasharray:7.2,7.2;&#34; /&gt;&#xA;&lt;text x=&#34;507.861&#34; y=&#34;43.1036&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;expect&lt;/text&gt;&#xA;&lt;text x=&#34;507.861&#34; y=&#34;63.2636&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;happy&lt;/text&gt;&#xA;&lt;text x=&#34;507.861&#34; y=&#34;83.4236&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;case&lt;/text&gt;&#xA;&lt;text x=&#34;507.861&#34; y=&#34;128.143&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;expect&lt;/text&gt;&#xA;&lt;text x=&#34;507.861&#34; y=&#34;148.303&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;corner&lt;/text&gt;&#xA;&lt;text x=&#34;507.861&#34; y=&#34;168.463&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;case 1&lt;/text&gt;&#xA;&lt;text x=&#34;507.861&#34; y=&#34;213.182&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;expect&lt;/text&gt;&#xA;&lt;text x=&#34;507.861&#34; y=&#34;233.342&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;corner&lt;/text&gt;&#xA;&lt;text x=&#34;507.861&#34; y=&#34;253.502&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;case 2&lt;/text&gt;&#xA;&lt;text x=&#34;201.719&#34; y=&#34;12.24&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;set up&lt;/text&gt;&#xA;&lt;text x=&#34;428.491&#34; y=&#34;12.24&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;verify result&lt;/text&gt;&#xA;&lt;text x=&#34;315.105&#34; y=&#34;12.24&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; dominant-baseline=&#34;central&#34;&gt;exercise&lt;/text&gt;&#xA;&lt;text x=&#34;116.68&#34; y=&#34;152.396&#34; text-anchor=&#34;middle&#34; fill=&#34;rgb(0,0,0)&#34; transform=&#34;rotate(-90 116.68,162.476)&#34; dominant-baseline=&#34;central&#34;&gt;modify&lt;/text&gt;&#xA;&lt;/svg&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;figcaption&gt;A diagram of how different tests share common test setup with a shared base fixture&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;&lt;strong&gt;Q&lt;/strong&gt;: How do you know you can trust your test set-up?&lt;br&gt;&#xA;&lt;strong&gt;A&lt;/strong&gt;: You reuse a well-known test set up in every test.&lt;/p&gt;&#xA;&lt;h2 id=&#34;example-code&#34;&gt;Example code&lt;/h2&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def test_simple():&#xA;  req = set_up_working_request()&#xA;  resp = invoke(req)&#xA;  assert resp.status == OK&#xA;&#xA;def test_invalid_page_size():&#xA;  req = set_up_working_request()&#xA;  req.page_size = -5&#xA;  resp = invoke(req)&#xA;  assert resp.status == INVALID_ARGUMENT&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;In the test,&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;We call &lt;code&gt;set_up_working_request()&lt;/code&gt; and we know this results in&#xA;&amp;ldquo;working&amp;rdquo; behavior.  Then we purposefully inject a defective&#xA;negative page size into the working request.&lt;/li&gt;&#xA;&lt;li&gt;We exercise the system under test in the same way.&lt;/li&gt;&#xA;&lt;li&gt;We expect a different result.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Because the only difference in set up between &lt;code&gt;test_simple&lt;/code&gt; and&#xA;&lt;code&gt;test_invalid_page_size&lt;/code&gt; was in the differing page size, we can easily&#xA;reason that the different response status is resulting from just that.&lt;/p&gt;&#xA;&lt;h2 id=&#34;related-techniques&#34;&gt;Related techniques&lt;/h2&gt;&#xA;&lt;p&gt;This works well together with these techniques:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Structure your test methods with &lt;strong&gt;separate set up, exercise and&#xA;verify phases&lt;/strong&gt;.&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;&#xA;&lt;li&gt;Use &lt;strong&gt;explicit helper functions for set up&lt;/strong&gt; and possibly name them&#xA;according to expected behavior such as &amp;ldquo;working&amp;rdquo; in the example&#xA;above.&lt;/li&gt;&#xA;&lt;li&gt;Keep the shared &lt;strong&gt;set up code in test methods as short as&#xA;possible&lt;/strong&gt;, so that it&amp;rsquo;s easy to spot the equal parts.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;1:N&lt;/strong&gt;: Use one base scenario (e.g. &amp;ldquo;working&amp;rdquo;) and use its set up&#xA;as a base for multiple derived scenarios.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;This is called the &amp;ldquo;Four-Phase test&amp;rdquo; in the xUnit&#xA;test pattern book. (&lt;a href=&#34;https://martinfowler.com/books/meszaros.html&#34;&gt;Meszaros07, p358&lt;/a&gt;)&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/base_fixture/</guid>
      <pubDate>Tue, 06 Nov 2018 07:44:44 +0100</pubDate>
    </item>
    <item>
      <title>No error left behind</title>
      <link>https://blog.gnoack.org/post/error_handling</link>
      <description>&lt;figure&gt;&#xA;&lt;img src=&#34;/images/reliability.svg&#34; alt=&#34;Cartoon in the style of webcomicname.com - first panel: Stakeholder: &amp;lsquo;Make it do X&amp;rsquo; Developer: &amp;lsquo;OK&amp;rsquo;; second panel: developer works on machine, thinks: &amp;lsquo;X X X X X&amp;rsquo;; third panel: user uses the machine saying &amp;lsquo;Y&amp;rsquo;. Machine: explodes. Developer: &amp;lsquo;oh no&amp;rsquo;&#34;&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;Which design guidelines should software follow in order to run&#xA;reliably?  Which guidelines do you use in your day-to-day work?&lt;/p&gt;&#xA;&lt;p&gt;Error handling is a significant complexity in real-world projects, but&#xA;programming textbooks are often giving very little advice on how to&#xA;think about it at the higher level.  This article tries to fill that&#xA;gap:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;We will define what an error is.&lt;/li&gt;&#xA;&lt;li&gt;We will find out that each error has a human stakeholder.&lt;/li&gt;&#xA;&lt;li&gt;We will discuss technical mechanisms to bring errors to human attention.&lt;/li&gt;&#xA;&lt;li&gt;The final section contains various examples of how to apply this.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;To handle the error cases in your software, you should ask yourself&#xA;who is the human stakeholder who would be able to address it, and then&#xA;find a suitable mechanism to escalate the error in the direction of&#xA;that stakeholder.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ve cross-checked this approach with multiple people from various&#xA;software backgrounds, but I&amp;rsquo;m still happy to receive your feedback.&lt;/p&gt;&#xA;&lt;h1 id=&#34;what-is-an-error&#34;&gt;What is an error?&lt;/h1&gt;&#xA;&lt;blockquote class=&#34;def&#34;&gt;&lt;p&gt;&lt;strong&gt;Definition:&lt;/strong&gt;&#xA;&lt;em&gt;An error&lt;/em&gt; is when the program is operating outside the intended path&#xA;of execution.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Running programs within the intended path of execution is a goal by&#xA;definition.  To track and reach this goal, we need to make it&#xA;observable whether the program is running healthily:&lt;/p&gt;&#xA;&lt;blockquote class=&#34;rule&#34;&gt;&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt;&#xA;All errors should be &lt;em&gt;observable&lt;/em&gt; from &lt;em&gt;the outside&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;&lt;em&gt;Observable&lt;/em&gt; in this context means that it&amp;rsquo;s possible to bring the&#xA;error to a human&amp;rsquo;s attention (e.g. end user, system administrator,&#xA;developer) in an automated fashion.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;The outside&lt;/em&gt; is defined by the bounds of your program.  The error&#xA;reports need to be made accessible to the surrounding environment,&#xA;so that stakeholders can access them.&lt;/p&gt;&#xA;&lt;h2 id=&#34;error-stakeholders&#34;&gt;Error stakeholders&lt;/h2&gt;&#xA;&lt;p&gt;The stakeholders for an error are the people involved in the software&#xA;lifecycle, such as: End users, developers, system administrators,&#xA;network administrators etc.&lt;/p&gt;&#xA;&lt;blockquote class=&#34;rule&#34;&gt;&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt;&#xA;For each error, there is a corresponding stakeholder.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/stakeholders.svg&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;Different errors have different stakeholders who can address the issue.&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;Is the error cause &lt;strong&gt;within the program itself&lt;/strong&gt;, then the&#xA;&lt;strong&gt;developer&lt;/strong&gt; is the stakeholder.&lt;/p&gt;&#xA;&lt;p&gt;Is the error cause &lt;strong&gt;in the user input or interaction&lt;/strong&gt; (e.g. invalid&#xA;input), then &lt;strong&gt;the user&lt;/strong&gt; is the stakeholder.&lt;/p&gt;&#xA;&lt;p&gt;Is the error cause &lt;strong&gt;in the execution environment&lt;/strong&gt;, then &lt;strong&gt;the&#xA;operator of that environment&lt;/strong&gt; is the stakeholder (e.g. sysadmin,&#xA;network admin, SRE).&lt;/p&gt;&#xA;&lt;h1 id=&#34;mechanisms&#34;&gt;Mechanisms&lt;/h1&gt;&#xA;&lt;h2 id=&#34;routing-errors-to-the-stakeholders&#34;&gt;Routing errors to the stakeholders&lt;/h2&gt;&#xA;&lt;p&gt;Routing error indicators to the right stakeholders is partially done&#xA;in code, and partially within the deployment.  Particularly,&#xA;displaying errors to end users is usually done in the program code&#xA;itself.  On the other hand, metrics collection set-ups can be&#xA;monitored by developers and administrators alike.&lt;/p&gt;&#xA;&lt;p&gt;There are two main strategies to make errors observable:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Propagate the error upwards towards the initiator of the erroring&#xA;operation.&lt;/li&gt;&#xA;&lt;li&gt;Propagate the error to the side (monitoring and logging).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;At least one of these should be used for any error.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/errors.svg&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;A function can propagate errors upwards or to the side,&lt;br&gt;at least one of these should be used.&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;h2 id=&#34;propagating-an-error-upwards&#34;&gt;Propagating an error upwards&lt;/h2&gt;&#xA;&lt;p&gt;Propagating &amp;ldquo;upwards&amp;rdquo; moves the responsibility of error handling to a&#xA;higher level.  In general, higher level software has more context for&#xA;the failing operation, so they are often in a better position to route&#xA;the error in the right direction.&lt;/p&gt;&#xA;&lt;p&gt;Propagating errors upwards can take many forms.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Function level: &lt;strong&gt;Propagate to the calling procedure&lt;/strong&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Return an error code&lt;/li&gt;&#xA;&lt;li&gt;Raise an exception&lt;/li&gt;&#xA;&lt;li&gt;Propagate a lower-level exception&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Process level: &lt;strong&gt;Crash the process&lt;/strong&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Exit the program with an error status (e.g. Unix &lt;a href=&#34;http://man7.org/linux/man-pages/man3/exit.3.html&#34;&gt;&lt;code&gt;exit()&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;&#xA;&lt;li&gt;Abort the program (e.g. Unix &lt;a href=&#34;http://man7.org/linux/man-pages/man3/abort.3.html&#34;&gt;&lt;code&gt;abort()&lt;/code&gt;&lt;/a&gt;)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;Network level: &lt;strong&gt;Return the error in a network response&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;User level: &lt;strong&gt;Show the error to the user&lt;/strong&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;e.g. in a UI dialog&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&#xA;Remember to convert the error into a representation which fits the&#xA;level of abstraction that the caller expects.  Many languages&#xA;support nesting (wrapping) errors so that the full context is&#xA;retained.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;propagating-an-error-to-the-side&#34;&gt;Propagating an error to the side&lt;/h2&gt;&#xA;&lt;p&gt;Sometimes passing the error upwards is not an option or not&#xA;sufficient, for example because you want to transparently recover or&#xA;hide a subsystem failure from the user.&lt;/p&gt;&#xA;&lt;p&gt;Propagating errors to the side can be one of the following:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Increment a counter which can be monitored from the outside.&#xA;(e.g. using Prometheus or Linux kernel stats counters)&lt;/li&gt;&#xA;&lt;li&gt;Write the error to an error collection service.&#xA;&lt;ul&gt;&#xA;&lt;li&gt;For server side software: Record stack traces and notify&#xA;developers about them.&lt;/li&gt;&#xA;&lt;li&gt;For client side software: Ask the user to send bug reports when&#xA;errors are encountered.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Make sure that all the relevant error propagation sinks are&#xA;monitored by the right stakeholders during the program&amp;rsquo;s operation.&lt;/p&gt;&#xA;&lt;p&gt;Systems like Prometheus are built with monitoring and alerting in&#xA;mind.  It&amp;rsquo;s often a good idea to alert on symptoms close to your&#xA;business needs and then use collected metrics on other errors for&#xA;further analysis&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&lt;blockquote class=&#34;warning&#34;&gt;&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt;&#xA;Textual logging is not an error handling strategy. Logs are not&#xA;meant for machine consumption, so it&amp;rsquo;s hard to have automated&#xA;monitoring based on them.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;blockquote class=&#34;warning&#34;&gt;&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt;&#xA;Ignoring errors is not an error handling strategy either.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;h2 id=&#34;what-if-my-case-is-not-a-fit&#34;&gt;What if my case is not a fit?&lt;/h2&gt;&#xA;&lt;p&gt;If your case it not a fit, it&amp;rsquo;s possible that the condition at hand&#xA;might not be an error to begin with, but is maybe only a &amp;ldquo;corner case&amp;rdquo;&#xA;which may happen in normal operation (e.g. a lookup key was not&#xA;found).&lt;/p&gt;&#xA;&lt;p&gt;Consider switching the way that the condition is propagated.  The&#xA;following options are alternatives where otherwise errors may be&#xA;used.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Null/empty object:&lt;/strong&gt; Returning empty lists, sets, &lt;code&gt;Optional&amp;lt;T&amp;gt;&lt;/code&gt;&#xA;types, or other &lt;a href=&#34;https://en.wikipedia.org/wiki/Null_object_pattern&#34;&gt;Null&#xA;Objects&lt;/a&gt;. Note&#xA;that this is not the same as &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Separate channel:&lt;/strong&gt; Return the non-error conditions through a&#xA;separate channel (e.g. don&amp;rsquo;t use an exception, but return&#xA;specially-built objects for these conditions).  &lt;strong&gt;Example:&lt;/strong&gt; A&#xA;linter tool&amp;rsquo;s output is produced as part of the expected operation&#xA;and is therefore not an error within that context.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Null:&lt;/strong&gt; Returning &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;nil&lt;/code&gt; or similar is idiomatic in some languages&#xA;too, but may lead to follow-up&#xA;errors&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;&#xA;when the value is dereferenced.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;If none of these worked for you, I&amp;rsquo;d love to hear from you,&#xA;so I can correct my understanding.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Sometimes the most elegant solution is to &lt;strong&gt;change the API to make the&#xA;error impossible&lt;/strong&gt;&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;. For example, replace a dynamic check with&#xA;one that&amp;rsquo;s statically guaranteed by the type system, or change to&#xA;idempotent operation semantics, so that multiple invocations do not&#xA;conflict with each other.&lt;/p&gt;&#xA;&lt;h1 id=&#34;examples&#34;&gt;Examples&lt;/h1&gt;&#xA;&lt;p&gt;These are all written from the perspective of a piece of code&#xA;detecting an error.&lt;/p&gt;&#xA;&lt;h2 id=&#34;recovery-through-redundancy&#34;&gt;Recovery through redundancy&lt;/h2&gt;&#xA;&lt;h3 id=&#34;example&#34;&gt;Example&lt;/h3&gt;&#xA;&lt;p&gt;A disk in a software RAID is failing.&lt;/p&gt;&#xA;&lt;h3 id=&#34;stakeholder&#34;&gt;Stakeholder&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;The system administrator&lt;/strong&gt;, as soon as the percentage of recovered executions &amp;gt; threshold.&#xA;They can then investigate the cause of network issues, exchange hard&#xA;drives or similar.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;mechanism&#34;&gt;Mechanism&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Recover&lt;/strong&gt; by retrying the operation on a different disk.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Increment an error counter&lt;/strong&gt; indicating the failure cause.  When&#xA;incremented in a monitorable system, this counter can trigger an alert.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;similar-cases&#34;&gt;Similar cases&lt;/h3&gt;&#xA;&lt;p&gt;A TCP network packet is lost. (Difference: Retry has less control over&#xA;the network routing)&lt;/p&gt;&#xA;&lt;h2 id=&#34;recovery-through-omission&#34;&gt;Recovery through omission&lt;/h2&gt;&#xA;&lt;h3 id=&#34;example-1&#34;&gt;Example&lt;/h3&gt;&#xA;&lt;p&gt;A web server uses multiple database backends.  One of the&#xA;non-essential backends starts to return errors.&lt;/p&gt;&#xA;&lt;h3 id=&#34;stakeholder-1&#34;&gt;Stakeholder&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The &lt;strong&gt;operator&lt;/strong&gt; of the failing subsystem.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;mechanism-1&#34;&gt;Mechanism&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Omit&lt;/strong&gt; the backend&amp;rsquo;s response.  (Treat it as if it didn&amp;rsquo;t exist.)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Increment an error counter&lt;/strong&gt; indicating the failure cause, so that&#xA;the subsystem operator can be alerted.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;delegating-the-decision-to-the-calling-procedure&#34;&gt;Delegating the decision to the calling procedure&lt;/h2&gt;&#xA;&lt;h3 id=&#34;example-2&#34;&gt;Example&lt;/h3&gt;&#xA;&lt;p&gt;The Unix &lt;code&gt;open()&lt;/code&gt; function is asked to open a given filename for&#xA;reading, but the file doesn&amp;rsquo;t exist.&lt;/p&gt;&#xA;&lt;h3 id=&#34;stakeholder-2&#34;&gt;Stakeholder&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;We don&amp;rsquo;t know&lt;/strong&gt; who is responsible for passing the wrong filename,&#xA;but somewhere in the call chain, someone is going to know where that&#xA;filename comes from.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;mechanism-2&#34;&gt;Mechanism&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Report the error to the caller&lt;/strong&gt; with the documented &lt;code&gt;ENOENT&lt;/code&gt; error code.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;similar-cases-1&#34;&gt;Similar cases&lt;/h3&gt;&#xA;&lt;p&gt;Mistakes in passed input values are best handled by the code which&#xA;passed them, and which can judge why they happened.&lt;/p&gt;&#xA;&lt;h2 id=&#34;reporting-upwards-and-sideways-at-the-same-time&#34;&gt;Reporting upwards and sideways at the same time&lt;/h2&gt;&#xA;&lt;h3 id=&#34;example-3&#34;&gt;Example&lt;/h3&gt;&#xA;&lt;p&gt;A web server&amp;rsquo;s servlets are returning errors in the form of HTTP&#xA;status codes.&lt;/p&gt;&#xA;&lt;h3 id=&#34;stakeholders&#34;&gt;Stakeholders&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The &lt;strong&gt;requester&lt;/strong&gt; is a stakeholder because they care about the&#xA;request.&lt;/li&gt;&#xA;&lt;li&gt;The web server&amp;rsquo;s &lt;strong&gt;operator&lt;/strong&gt; is a stakeholder because they care&#xA;about keeping overall error fractions within reasonable bounds.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;mechanisms-1&#34;&gt;Mechanisms&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Propagate status via HTTP&lt;/strong&gt;, upwards to the caller who has more&#xA;context.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Count HTTP statuses&lt;/strong&gt; keyed by relevant criteria (such as&#xA;servlet), e.g. in Prometheus, for the web server operator.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;A good background read is the &lt;a href=&#34;https://landing.google.com/sre/book/chapters/monitoring-distributed-systems.html#symptoms-versus-causes-g0sEi4&#34;&gt;Site Reliability Engineering Book&lt;/a&gt;, section &amp;ldquo;Symptoms vs. Causes&amp;rdquo;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions&#34;&gt;Wikipedia: Tony Hoare, section &amp;ldquo;Apologies and retractions&amp;rdquo;&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;The book &lt;a href=&#34;https://www.goodreads.com/book/show/39996759-a-philosophy-of-software-design&#34;&gt;Philosophy of Software Design&lt;/a&gt; by John Ousterhout has an entire chapter on the idea of defining errors away.  The author has also given a &lt;a href=&#34;https://www.youtube.com/watch?v=bmSAYlu0NcYt&#34;&gt;tech talk&lt;/a&gt;.&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/error_handling/</guid>
      <pubDate>Mon, 03 Sep 2018 22:36:41 +0200</pubDate>
    </item>
    <item>
      <title>There are my core dumps!</title>
      <link>https://blog.gnoack.org/post/there-are-my-core-dumps</link>
      <description>&lt;p&gt;&lt;a href=&#34;https://www.unix-ag.uni-kl.de/~guenther/where-are-my-core-dumps.html&#34;&gt;As noted&#xA;before&lt;/a&gt;,&#xA;with &lt;code&gt;systemd&lt;/code&gt;, your core dumps disappear into one of &lt;code&gt;systemd&lt;/code&gt;&amp;rsquo;s log&#xA;sinks, instead of being stored to the current directory. By now, they&#xA;are recoverable by normal users again.&lt;/p&gt;&#xA;&lt;p&gt;TL;DR: Run &lt;code&gt;coredumpctl gdb&lt;/code&gt; to invoke &lt;code&gt;gdb&lt;/code&gt; with the most recent core dump.&lt;/p&gt;&#xA;&lt;p&gt;Related bug about disappearing coredumps:&#xA;&lt;a href=&#34;https://bugs.freedesktop.org/show_bug.cgi?id=54288&#34;&gt;https://bugs.freedesktop.org/show_bug.cgi?id=54288&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Scenario: A simple program that crashes with a segfault:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;~/dumpcore$ cat dumpcore.c&#xA;#include &amp;lt;unistd.h&amp;gt;&#xA;void dump_core()   { int* boom = NULL; *boom = 2000; }&#xA;void its_time_to() { dump_core(); }&#xA;int main()         { its_time_to(); }&#xA;~/dumpcore$ cc -g -o dumpcore dumpcore.c&#xA;~/dumpcore$ ./dumpcore&#xA;Segmentation fault (core dumped)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Core dumps get written to &lt;code&gt;systemd&lt;/code&gt;&amp;rsquo;s core dumps directory (see &lt;code&gt;man coredumpctl&lt;/code&gt;), and can be retrieved with the &lt;code&gt;coredumpctl&lt;/code&gt; tool:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;~/dumpcore$ coredumpctl list&#xA;TIME                            PID   UID   GID SIG PRESENT EXE&#xA;...&#xA;Sun 2016-10-02 14:44:14 CEST   3857  1000   100  11 * /home/me/dumpcore/dumpcore&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;By default, &lt;code&gt;coredumpctl&lt;/code&gt; seems to use the most recent core dump,&#xA;so that you can easily inspect the most recent core dump with:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;~/dumpcore$ coredumpctl gdb&#xA;           PID: 3857 (dumpcore)&#xA;           UID: 1000 (me)&#xA;           GID: 100 (users)&#xA;        Signal: 11 (SEGV)&#xA;     Timestamp: Sun 2016-10-02 14:44:14 CEST (1min 3s ago)&#xA;  Command Line: ./dumpcore&#xA;    Executable: /home/me/dumpcore/dumpcore&#xA; Control Group: /user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service&#xA;          Unit: user@1000.service&#xA;     User Unit: user@1000.service&#xA;         Slice: user-1000.slice&#xA;     Owner UID: 1000 (me)&#xA;       Boot ID: 73902fbce4e241f5bab007aad96895eb&#xA;    Machine ID: 3ff4d30a94914397acd6af26eed15114&#xA;      Hostname: limbo&#xA;      Coredump: /var/lib/systemd/coredump/core.dumpcore.1000.73902fbce4e241f5bab007aad96895eb.3857.1475412254000000000000.lz4&#xA;       Message: Process 3857 (dumpcore) of user 1000 dumped core.&#xA;&#xA;                Stack trace of thread 3857:&#xA;                #0  0x00000000004004b6 dump_core (dumpcore)&#xA;                #1  0x00000000004004cd its_time_to (dumpcore)&#xA;                #2  0x00000000004004de main (dumpcore)&#xA;                #3  0x00007f89ba351291 __libc_start_main (libc.so.6)&#xA;                #4  0x00000000004003da _start (dumpcore)&#xA;&#xA;GNU gdb (GDB) 7.11.1&#xA;Copyright (C) 2016 Free Software Foundation, Inc.&#xA;License GPLv3+: GNU GPL version 3 or later &amp;lt;http://gnu.org/licenses/gpl.html&amp;gt;&#xA;This is free software: you are free to change and redistribute it.&#xA;There is NO WARRANTY, to the extent permitted by law.  Type &amp;quot;show copying&amp;quot;&#xA;and &amp;quot;show warranty&amp;quot; for details.&#xA;This GDB was configured as &amp;quot;x86_64-pc-linux-gnu&amp;quot;.&#xA;Type &amp;quot;show configuration&amp;quot; for configuration details.&#xA;For bug reporting instructions, please see:&#xA;&amp;lt;http://www.gnu.org/software/gdb/bugs/&amp;gt;.&#xA;Find the GDB manual and other documentation resources online at:&#xA;&amp;lt;http://www.gnu.org/software/gdb/documentation/&amp;gt;.&#xA;For help, type &amp;quot;help&amp;quot;.&#xA;Type &amp;quot;apropos word&amp;quot; to search for commands related to &amp;quot;word&amp;quot;...&#xA;Reading symbols from /home/me/dumpcore/dumpcore...done.&#xA;[New LWP 3857]&#xA;&#xA;warning: Could not load shared library symbols for linux-vdso.so.1.&#xA;Do you need &amp;quot;set solib-search-path&amp;quot; or &amp;quot;set sysroot&amp;quot;?&#xA;Core was generated by `./dumpcore&#39;.&#xA;Program terminated with signal SIGSEGV, Segmentation fault.&#xA;#0  0x00000000004004b6 in dump_core () at dumpcore.c:2&#xA;2&#x9;void dump_core()   { int* boom = NULL; *boom = 2000; }&#xA;(gdb) bt&#xA;#0  0x00000000004004b6 in dump_core () at dumpcore.c:2&#xA;#1  0x00000000004004cd in its_time_to () at dumpcore.c:3&#xA;#2  0x00000000004004de in main () at dumpcore.c:4&#xA;(gdb)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/there-are-my-core-dumps/</guid>
      <pubDate>Sun, 02 Oct 2016 16:45:00 +0100</pubDate>
    </item>
    <item>
      <title>GPG with the CCID Driver</title>
      <link>https://blog.gnoack.org/post/gpg-ccid</link>
      <description>&lt;blockquote class=&#34;info&#34;&gt;&lt;p&gt;&lt;strong&gt;Remark&lt;/strong&gt;: I have not had this problem in years, and it&amp;rsquo;s probably long resolved.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Unless you&amp;rsquo;re using Debian, where people care about this, chances are&#xA;that setting up GPG Smartcards with your Linux distribution is an&#xA;adventure.  There are two ways to do it, CCID and PCSCLite, the latter&#xA;of which runs a background service &lt;code&gt;pcscd&lt;/code&gt; &amp;ndash; as root, at least on Arch,&#xA;while GnuPG&amp;rsquo;s built-in CCID driver accesses the reader directly&#xA;through the USB device, but took me a lot longer to figure out how to&#xA;use.&lt;/p&gt;&#xA;&lt;h2 id=&#34;problem&#34;&gt;Problem&lt;/h2&gt;&#xA;&lt;p&gt;You&amp;rsquo;re getting a &amp;ldquo;card error&amp;rdquo; when trying to access the card.  The&#xA;same thing works as root.&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ gpg --card-status&#xA;gpg: selecting openpgp failed: Card error&#xA;gpg: OpenPGP card not available: Card error&#xA;$&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;When trying the same as root, you can access the card.&lt;/p&gt;&#xA;&lt;h2 id=&#34;solution&#34;&gt;Solution&lt;/h2&gt;&#xA;&lt;p&gt;Add a &lt;code&gt;udev&lt;/code&gt; rule that changes file permissions for you, as documented on&#xA;&lt;a href=&#34;https://wiki.archlinux.org/index.php/GnuPG#Smartcard_not_detected&#34;&gt;https://wiki.archlinux.org/index.php/GnuPG#Smartcard_not_detected&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Add your user to a new user group that can read smartcards, and give&#xA;that group read-write access to the device when it&amp;rsquo;s plugged in, by&#xA;creating a file &lt;code&gt;/etc/udev/rules.d&lt;/code&gt; with the rule:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code class=&#34;language-txt&#34;&gt;ACTION==&amp;quot;add&amp;quot;, SUBSYSTEM==&amp;quot;usb&amp;quot;, ENV{ID_VENDOR_ID}==&amp;quot;08e6&amp;quot;, ENV{ID_MODEL_ID}==&amp;quot;3438&amp;quot;, MODE=&amp;quot;660&amp;quot;, GROUP=&amp;quot;scard&amp;quot;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;(Figure out the values for the vendor and model ID using &lt;code&gt;lsusb&lt;/code&gt;, they are printed separated with a colon.)&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/gpg-ccid/</guid>
      <pubDate>Mon, 15 Aug 2016 12:00:00 +0100</pubDate>
    </item>
    <item>
      <title>Hexiamonds</title>
      <link>https://blog.gnoack.org/post/hexiamonds</link>
      <description>&lt;p&gt;I made 3D models for Hexiamond puzzle pieces, which can be &lt;a href=&#34;https://www.unix-ag.uni-kl.de/~guenther/downloads/hexiamonds.tar.gz&#34;&gt;downloaded here&lt;/a&gt; (STL format).&lt;/p&gt;&#xA;&lt;p&gt;Each Hexiamond piece consists of 6 triangles, which yields 12&#xA;different pieces when you try out all combinations.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/hexiamond.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;3d model of a Hexiamond&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;More background information and some interesting puzzles to try can be&#xA;found on this nice page by David Goodger:&#xA;&lt;a href=&#34;http://puzzler.sourceforge.net/docs/hexiamonds.html&#34;&gt;http://puzzler.sourceforge.net/docs/hexiamonds.html&lt;/a&gt;&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/hexiamonds/</guid>
      <pubDate>Sun, 05 Jun 2016 12:00:00 +0100</pubDate>
    </item>
    <item>
      <title>Python-like generator functions</title>
      <link>https://blog.gnoack.org/post/generators</link>
      <description>&lt;p&gt;Python-like generator functions, implemented as a library:&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;/images/fn-generators.png&#34; alt=&#34;&#34;&gt;&#xA;&lt;figcaption&gt;&lt;p&gt;Generator functions implemented as a library&lt;/p&gt;&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;This is possible through two simple tricks:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;The language represents stack frames as objects on the heap.&lt;/li&gt;&#xA;&lt;li&gt;There&amp;rsquo;s a native function for grabbing the current stack frame.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Turns out replacing the calling stack frame is really the only thing&#xA;you need in order to implement coroutines and generics.&lt;/p&gt;&#xA;&lt;p&gt;The implementation is only 23 lines long.  On my screen, including the&#xA;screenshot, this weblog article is already longer up until here.&lt;/p&gt;&#xA;</description>
      <guid>https://blog.gnoack.org/post/generators/</guid>
      <pubDate>Sun, 16 Nov 2014 19:41:00 +0100</pubDate>
    </item>
  </channel>
</rss>