<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>adangel.org</title>
    <description>Personal blog of Andreas Dangel about programming, computer, technology</description>
    <link>https://adangel.org/</link>
    <atom:link href="https://adangel.org/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Sun, 22 Feb 2026 12:00:08 +0100</pubDate>
    <lastBuildDate>Sun, 22 Feb 2026 12:00:08 +0100</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>PDF to image conversion workaround</title>
        <description>&lt;h2 id=&quot;motivation&quot;&gt;Motivation&lt;/h2&gt;

&lt;p&gt;I’m using &lt;a href=&quot;https://readera.org/&quot;&gt;ReadEra&lt;/a&gt; to read magazines in PDF format. It usually works fine.
But yesterday, one PDF didn’t show up correctly. It looks like some font displaying problem.
The text is there, but not visible. You could select it, but nothing is displayed. Such text is
a bit hard to read…&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2026-02-22-pdf-image-conversions/readera-screenshot-before.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;cover of the magazin before applying the workaround&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;the-workaround&quot;&gt;The workaround&lt;/h2&gt;
&lt;p&gt;Since the PDF looks good in other applications, I don’t think it is a bad PDF. The PDF has lots of
fonts embedded, but so did the earlier issues of the magazines in the past, which were displayed
correctly.&lt;/p&gt;

&lt;p&gt;The simple try to print the PDF with another application again to PDF didn’t fix the problem: The
text was still missing.&lt;/p&gt;

&lt;p&gt;So there is another workaround needed: We just convert the whole PDF into a series of images and then
create a new PDF from these images. That way, there should be no problems in displaying the PDF since
it consists only of images. The downside however is, that the PDF is now entirely made up of images
and no text. But as a human, I’m still good at converting text on images into text myself.&lt;/p&gt;

&lt;h3 id=&quot;step-1&quot;&gt;Step 1&lt;/h3&gt;
&lt;p&gt;You’ll need &lt;a href=&quot;https://ghostscript.com/index.html&quot;&gt;ghostscript&lt;/a&gt;, which is often already installed on
Linux systems.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir pdfimages
gs -sDEVICE=png16m -sOutputFile=pdfimages/page-%03d.jpg -r300x300 -dNOPAUSE -dBATCH -f input.pdf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will convert &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input.pdf&lt;/code&gt; into a series of jpeg images into the folder &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdfimages&lt;/code&gt;.
You can simply verify, that the pages are displayed correctly. In case you see the same missing text,
then this workaround won’t help you - then Ghostscript has the same issue as ReadEra.&lt;/p&gt;

&lt;h3 id=&quot;step-2&quot;&gt;Step 2&lt;/h3&gt;
&lt;p&gt;Now we’ll combine all the pages back into a new handy PDF using &lt;a href=&quot;https://gitlab.mister-muffin.de/josch/img2pdf&quot;&gt;img2pdf&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;img2pdf pdfimages/*.jpg -o input_images.pdf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we have again a single PDF and since it only consists of images, there won’t be a
display problem.&lt;/p&gt;

&lt;h2 id=&quot;result&quot;&gt;Result&lt;/h2&gt;

&lt;p&gt;The result looks like this:&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2026-02-22-pdf-image-conversions/readera-screenshot-after.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;cover of the magazin after applying the workaround&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The file size of the new PDF is much bigger now. You might experiment with more options when generating the jpegs
or the PDF (e.g. resolution, compression). But as a workaround, this is good enough.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/27740379/pdf-to-jpeg-conversion-using-ghostscript&quot;&gt;https://stackoverflow.com/questions/27740379/pdf-to-jpeg-conversion-using-ghostscript&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/4283245/using-ghostscript-to-convert-jpeg-to-pdf&quot;&gt;https://stackoverflow.com/questions/4283245/using-ghostscript-to-convert-jpeg-to-pdf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sun, 22 Feb 2026 00:00:00 +0100</pubDate>
        <link>https://adangel.org/2026/02/22/pdf-image-conversions/</link>
        <guid isPermaLink="true">https://adangel.org/2026/02/22/pdf-image-conversions/</guid>
        
        <category>pdf</category>
        
        <category>ebook</category>
        
        
      </item>
    
      <item>
        <title>glibc trickery - part 2</title>
        <description>&lt;p&gt;It turned out, that the last post &lt;a href=&quot;/2025/08/16/glibc-trickery/&quot;&gt;Using glibc trickery to run nodejs&lt;/a&gt;
isn’t sufficient in one special case: If you are using yarn…&lt;/p&gt;

&lt;h2 id=&quot;yarn&quot;&gt;Yarn&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://yarnpkg.com/&quot;&gt;Yarn&lt;/a&gt; is another package manager for JavaScript projects. It can replace &lt;a href=&quot;https://docs.npmjs.com/about-npm&quot;&gt;npm&lt;/a&gt;,
which is bundled with node nowadays. Yarn has a specific command: &lt;a href=&quot;https://yarnpkg.com/cli/node&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node&lt;/code&gt;&lt;/a&gt;.
This allows you to run node again - but not some node. Yarn tries to make sure, that the exact same node version is used as the one
that is used to run Yarn itself.&lt;/p&gt;

&lt;p&gt;So, one could think, that yarn achieves this, by looking at “argv0” - the zero argument of the argument list of the process.
We can actually run a small node script to check, which value we get.&lt;/p&gt;

&lt;p&gt;Given the same setup from the previous blog post, we can run this command now:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;console.log(process.argv0);&apos;&lt;/span&gt;
/root/node-test/node-v24.6.0-linux-x64/bin/node
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Hm, ok. This looks ok. However, if we would try to start a new process, we would run in the same GLIBC version
issues again. It would be better, if this would have been the wrapper script already.&lt;/p&gt;

&lt;h2 id=&quot;changing-argv0&quot;&gt;Changing argv0&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process.argv0&lt;/code&gt; is documented here: &lt;a href=&quot;https://nodejs.org/docs/latest/api/process.html#processargv0&quot;&gt;https://nodejs.org/docs/latest/api/process.html#processargv0&lt;/a&gt;.
There is also &lt;a href=&quot;https://nodejs.org/docs/latest/api/process.html#processargv&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process.argv&lt;/code&gt;&lt;/a&gt;, which contains the whole
argument array, that has been passed to the nodejs process. Let’s see, what we get:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;console.log(`argv0=${process.argv0}\nargv=${process.argv}\n`);&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;argv0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/node-v24.6.0-linux-x64/bin/node
&lt;span class=&quot;nv&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/glibc-2.38/lib64/ld-linux-x86-64.so.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s try to change it to the wrapper script. The wrapper script is located at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/root/node-test/node&lt;/code&gt; and is using
the ld.loader. This has an argument called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--argv0&lt;/code&gt;, which let’s you overwrite exactly this piece
of information. This feature has been implemented with glibc 2.33, see &lt;a href=&quot;https://sourceware.org/bugzilla/show_bug.cgi?id=16124&quot;&gt;bug 16124&lt;/a&gt;
and &lt;a href=&quot;https://www.man7.org/linux/man-pages/man8/ld.so.8.html&quot;&gt;man ld.so.8&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The new wrapper script looks like this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;NODE_HOME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/node-v24.6.0-linux-x64
&lt;span class=&quot;nv&quot;&gt;GLIBC_HOME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/glibc-2.38

&lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GLIBC_HOME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/lib64/ld-linux-x86-64.so.2 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--argv0&lt;/span&gt; /root/node-test/node &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--library-path&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GLIBC_HOME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/lib64 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;NODE_HOME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/bin/node &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Running node again, we get now this:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;console.log(`argv0=${process.argv0}\nargv=${process.argv}\n`);&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;argv0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/node
&lt;span class=&quot;nv&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/glibc-2.38/lib64/ld-linux-x86-64.so.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Much better. “argv0” looks correct. Hopefully, argv doesn’t matter. But wait, where are node’s actual
arguments like “-e” and “console.log(….)”? It turns out, these are in another variable:
&lt;a href=&quot;https://nodejs.org/docs/latest/api/process.html#processexecargv&quot;&gt;process.execArgv&lt;/a&gt;. There is also
&lt;a href=&quot;https://nodejs.org/docs/latest/api/process.html#processexecpath&quot;&gt;process.execPath&lt;/a&gt; which we’ll need later.
Let’s have a look at all of these variables now:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;console.log(`argv0=${process.argv0}\nargv=${process.argv}\nexecPath=${process.execPath}\nexecArgv=${process.execArgv}\n`);&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;argv0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/node
&lt;span class=&quot;nv&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/glibc-2.38/lib64/ld-linux-x86-64.so.2
&lt;span class=&quot;nv&quot;&gt;execPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/glibc-2.38/lib64/ld-linux-x86-64.so.2
&lt;span class=&quot;nv&quot;&gt;execArgv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt;,console.log&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;argv0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.argv0&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.argv&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;execPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.execPath&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;execArgv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.execArgv&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ok, so far so good.&lt;/p&gt;

&lt;h2 id=&quot;executing-yarn&quot;&gt;Executing yarn&lt;/h2&gt;
&lt;p&gt;Let’s see, whether we can run “yarn node –version”. First of all, we need to get yarn.&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; corepack
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    yarn init &lt;span class=&quot;nt&quot;&gt;-2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;First command works, second failed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;executable file `yarn` not found in $PATH&lt;/code&gt;. Usually, it should
install it into the same directory as node/corepack, which would have been &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/root/node-test/node-v24.6.0-linux-x64/bin&lt;/code&gt;,
but these symlinks actually ended up in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/root/node-test/glibc-2.38/bin/&lt;/code&gt;… Let’s ignore this for now
and install yarn manually by downloading the single js file:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wget &lt;span class=&quot;nt&quot;&gt;-O&lt;/span&gt; yarn-4.10.3.js https://repo.yarnpkg.com/4.10.3/packages/yarnpkg-cli/bin/yarn.js
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node yarn-4.10.3.js &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
4.10.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This works - so we can at least start “yarn” now.&lt;/p&gt;

&lt;p&gt;Note: I’ve added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-w /root/node-test&lt;/code&gt; to set the current working directory, so that “yarn-4.10.3.js” can be referenced
relatively.&lt;/p&gt;

&lt;p&gt;What is the result of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn node --version&lt;/code&gt;?&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node yarn-4.10.3.js node &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
Usage Error: No project found &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; /root/node-test
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ok, we first need to initialize a project via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn init&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yarn install&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node yarn-4.10.3.js init
...
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node yarn-4.10.3.js &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;
...
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node yarn-4.10.3.js node &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
ld.so &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;GNU libc&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; stable release version 2.38.
Copyright &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;C&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 2023 Free Software Foundation, Inc.
This is free software&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; see the &lt;span class=&quot;nb&quot;&gt;source &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;copying conditions.
There is NO warranty&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; not even &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s worrying: Instead of node’s version, we see some output from “ld.so”. This means,
yarn wasn’t able to start a new node process.&lt;/p&gt;

&lt;h2 id=&quot;how-yarn-determines-the-node-used-to-run-itself&quot;&gt;How yarn determines the node used to run itself&lt;/h2&gt;

&lt;p&gt;This tooks some digging into yarn’s source code, which is available on GitHub under
&lt;a href=&quot;https://github.com/yarnpkg/berry&quot;&gt;yarnpkg/berry&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When you execute “yarn node”, yarn internally actually executes “yarn exec node” (see &lt;a href=&quot;https://github.com/yarnpkg/berry/blob/8ff18d709a4211f92837ff2f59eaf4972ca579c0/packages/plugin-essentials/sources/commands/node.ts#L26&quot;&gt;node.ts&lt;/a&gt;).
This is implemented in &lt;a href=&quot;https://github.com/yarnpkg/berry/blob/8ff18d709a4211f92837ff2f59eaf4972ca579c0/packages/plugin-essentials/sources/commands/exec.ts#L37&quot;&gt;exec.ts&lt;/a&gt; and calls the method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;executePackageShellcode&lt;/code&gt; in &lt;a href=&quot;https://github.com/yarnpkg/berry/blob/8ff18d709a4211f92837ff2f59eaf4972ca579c0/packages/yarnpkg-core/sources/scriptUtils.ts#L480&quot;&gt;scriptUtils.ts&lt;/a&gt; which sets up an environment, where executing just “node” will
execute the correct node process. This is done through wrapper scripts by method &lt;a href=&quot;https://github.com/yarnpkg/berry/blob/8ff18d709a4211f92837ff2f59eaf4972ca579c0/packages/yarnpkg-core/sources/scriptUtils.ts#L108&quot;&gt;makeScriptEnv&lt;/a&gt; and
several calls to &lt;a href=&quot;https://github.com/yarnpkg/berry/blob/8ff18d709a4211f92837ff2f59eaf4972ca579c0/packages/yarnpkg-core/sources/scriptUtils.ts#L40&quot;&gt;makePathWrapper&lt;/a&gt;. The argument “argv0” that is used to execute, is passed from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process.execPath&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So, yarn actually is going to execute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process.execPath&lt;/code&gt; in order to start a new node process… But we have seen above,
that this value is the loader… and not our node wrapper script.&lt;/p&gt;

&lt;p&gt;This explains, why “yarn node” doesn’t work:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node yarn-4.10.3.js node
/root/node-test/glibc-2.38/lib64/ld-linux-x86-64.so.2: missing program name
Try &lt;span class=&quot;s1&quot;&gt;&apos;/root/node-test/glibc-2.38/lib64/ld-linux-x86-64.so.2 --help&apos;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;more information.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, the question now became: “How to change process.execPath?”&lt;/p&gt;

&lt;h2 id=&quot;how-does-node-determine-processexecpath&quot;&gt;How does node determine process.execPath?&lt;/h2&gt;

&lt;p&gt;Let’s have a look at the implementation of node. In &lt;a href=&quot;https://github.com/nodejs/node/blob/2e5c8dff9cb1208fa97465ae0fc63abf2213e9e5/src/node_process_object.cc#L219-L224&quot;&gt;node_process_object.cc&lt;/a&gt; there is some code for handling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;execPath&lt;/code&gt;. It reads the value from “env”,
which is of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Environment&lt;/code&gt;, which is implemented in &lt;a href=&quot;https://github.com/nodejs/node/blob/main/src/env.cc&quot;&gt;env.cc&lt;/a&gt;. This
contains a lot of “exec_path” references. In the end, it seems to be initialized in the &lt;a href=&quot;https://github.com/nodejs/node/blob/2e5c8dff9cb1208fa97465ae0fc63abf2213e9e5/src/env.cc#L806&quot;&gt;constructor&lt;/a&gt;
by calling the method &lt;a href=&quot;https://github.com/nodejs/node/blob/2e5c8dff9cb1208fa97465ae0fc63abf2213e9e5/src/env.cc#L762&quot;&gt;Environment::GetExecPath&lt;/a&gt;:
This is calling the platform dependent &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv_exepath&lt;/code&gt;, which is implemented for linux in &lt;a href=&quot;https://github.com/nodejs/node/blob/2e5c8dff9cb1208fa97465ae0fc63abf2213e9e5/deps/uv/src/unix/procfs-exepath.c#L28&quot;&gt;procfs-exepath.c&lt;/a&gt;, which just reads the symlink &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/proc/self/exe&lt;/code&gt;.
By the way, if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uv_exepath&lt;/code&gt; would return an error, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Environment::GetExecPath&lt;/code&gt; would fall back to argv0…&lt;/p&gt;

&lt;p&gt;Ok, can we somehow change, what the symlink in /proc/self/exe it pointing at?&lt;/p&gt;

&lt;h2 id=&quot;changing-procselfexe&quot;&gt;Changing /proc/self/exe&lt;/h2&gt;

&lt;p&gt;Apparently this is very difficult, for good reasons. See e.g. &lt;a href=&quot;https://haxrob.net/hiding-in-plain-sight-part-2/&quot;&gt;Hiding in plain sight - Abusing the dynamic linker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, there is the LD_PRELOAD mechanism, also known as the &lt;a href=&quot;https://stackoverflow.com/questions/426230/what-is-the-ld-preload-trick&quot;&gt;ld preload trick&lt;/a&gt;.
With glibc you can load a shared library early on (“preload”) and this can intercept other library methods. Since node is using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;readlink&lt;/code&gt;,
we can intercept this call and return whatever path we want for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/proc/self/exe&lt;/code&gt;… that should do the trick.&lt;/p&gt;

&lt;p&gt;So, how does this work? We need a shared library. It is based on &lt;a href=&quot;https://stackoverflow.com/questions/69859/how-could-i-intercept-linux-sys-calls&quot;&gt;How could I intercept linux syscalls?&lt;/a&gt;.
Let’s call it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;change_exe.c&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Based on https://stackoverflow.com/questions/69859/how-could-i-intercept-linux-sys-calls&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#define _GNU_SOURCE
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;dlfcn.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
#include&lt;/span&gt; &lt;span class=&quot;cpf&quot;&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;
&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;extern&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errorno&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;ssize_t&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;__thread&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_readlink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;restrict&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bufsiz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;ssize_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;readlink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;restrict&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bufsiz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_readlink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_readlink&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ssize_t&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;restrict&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bufsiz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dlsym&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RTLD_NEXT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;readlink&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strcmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/proc/self/exe&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CHANGE_EXE_PATH&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;size_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env_path_len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strlen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;env_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;strncpy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bufsiz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bufsiz&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env_path_len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;env_path_len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bufsiz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_readlink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;buf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bufsiz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// gcc -fPIC -shared -ldl -o change_exe.so change_exe.c&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Test 1: LD_PRELOAD=&quot;./change_exe.so&quot; readlink /proc/self/exe&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Test 2: CHANGE_EXE_PATH=&quot;/foo&quot; LD_PRELOAD=&quot;./change_exe.so&quot; readlink /proc/self/exe&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This library will check whether the environment variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CHANGE_EXE_PATH&lt;/code&gt; exists and will use this
one when readlink is called for /proc/self/exe. This way, we can configure the actual path.&lt;/p&gt;

&lt;h2 id=&quot;final-wrapper-script&quot;&gt;Final wrapper script&lt;/h2&gt;

&lt;p&gt;We can now change our wrapper script to include not only “argv0” but also a preloaded library:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;NODE_HOME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/node-v24.6.0-linux-x64
&lt;span class=&quot;nv&quot;&gt;GLIBC_HOME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/glibc-2.38
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CHANGE_EXE_PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/node

&lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GLIBC_HOME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/lib64/ld-linux-x86-64.so.2 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--argv0&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CHANGE_EXE_PATH&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--preload&lt;/span&gt; /root/node-test/change_exe.so &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--library-path&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;GLIBC_HOME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/lib64 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;NODE_HOME&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;/bin/node &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Does it work?&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node yarn-4.10.3.js node &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
v24.6.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Yes! The whole chain node -&amp;gt; yarn -&amp;gt; node is working now. Let’s see, what the values &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process.execPath&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;process.argv&lt;/code&gt; and
so on are now:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;podman run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--pull&lt;/span&gt; newer &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/root/node-test &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    docker.io/opensuse/leap:42 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    node &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;console.log(`argv0=${process.argv0}\nargv=${process.argv}\nexecPath=${process.execPath}\nexecArgv=${process.execArgv}\n`);&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;argv0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/node
&lt;span class=&quot;nv&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/node
&lt;span class=&quot;nv&quot;&gt;execPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/node
&lt;span class=&quot;nv&quot;&gt;execArgv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt;,console.log&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;argv0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.argv0&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.argv&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;execPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.execPath&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;execArgv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.execArgv&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;While researching this topic, I remembered &lt;a href=&quot;https://proot-me.github.io/&quot;&gt;proot&lt;/a&gt; which I occasionally use for backups
(see &lt;a href=&quot;/2024/08/29/backups-with-restic-and-proot/&quot;&gt;Backups with restic and proot&lt;/a&gt;). proot as well intercepts system calls
and returns “adjusted” values - it especially intercepts all calls that deal with path names. But do they do anything about
/proc/self/exe? Let’s see:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./proot node-v24.6.0-linux-x64/bin/node &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;console.log(`argv0=${process.argv0}\nargv=${process.argv}\nexecPath=${process.execPath}\nexecArgv=${process.execArgv}\n`);&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;argv0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;node-v24.6.0-linux-x64/bin/node
&lt;span class=&quot;nv&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/node-v24.6.0-linux-x64/bin/node
&lt;span class=&quot;nv&quot;&gt;execPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/root/node-test/node-v24.6.0-linux-x64/bin/node
&lt;span class=&quot;nv&quot;&gt;execArgv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt;,console.log&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;argv0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.argv0&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;argv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.argv&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;execPath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.execPath&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;execArgv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.execArgv&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, they obviously thought about this… However, proot works a bit different: While “change_exe.c” only intercepts a glibc library
method “readlink”, which itself is wrapper function around the actual syscall, proot uses &lt;a href=&quot;https://linux.die.net/man/2/ptrace&quot;&gt;ptrace&lt;/a&gt;
to intercept the actual syscalls. And it seems to follow processes: if you run a shell with proot and start another process from there,
this child process also have its syscall intercepted.&lt;/p&gt;

&lt;p&gt;Also note, that all this with wrapper scripts is only necessary, if you a) cannot upgrade the system
(hey, why not using a modern operating system?) and b) you cannot change the node binaries.&lt;/p&gt;

&lt;p&gt;Because if you can change the node binaries, you can use &lt;a href=&quot;https://github.com/NixOS/patchelf&quot;&gt;patchelf&lt;/a&gt; to set
the loader and library path directly in the node binary. Then you don’t need to call node explicitly via the
loader anymore - it will just work. This is explained in &lt;a href=&quot;https://stackoverflow.com/questions/847179/multiple-glibc-libraries-on-a-single-host&quot;&gt;Multiple glibc libraries on a single host&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;
&lt;p&gt;Here are some pages in used during research in no particular order:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://sourceware.org/bugzilla/show_bug.cgi?id=16124&quot;&gt;https://sourceware.org/bugzilla/show_bug.cgi?id=16124&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/847179/multiple-glibc-libraries-on-a-single-host&quot;&gt;https://stackoverflow.com/questions/847179/multiple-glibc-libraries-on-a-single-host&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Dynamic_linker&quot;&gt;https://en.wikipedia.org/wiki/Dynamic_linker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://haxrob.net/process-name-stomping/&quot;&gt;https://haxrob.net/process-name-stomping/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://haxrob.net/hiding-in-plain-sight-part-2/&quot;&gt;https://haxrob.net/hiding-in-plain-sight-part-2/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://superuser.com/questions/1144758/overwrite-default-lib64-ld-linux-x86-64-so-2-to-call-executables&quot;&gt;https://superuser.com/questions/1144758/overwrite-default-lib64-ld-linux-x86-64-so-2-to-call-executables&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://lwn.net/Articles/631631/&quot;&gt;https://lwn.net/Articles/631631/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/69859/how-could-i-intercept-linux-sys-calls&quot;&gt;https://stackoverflow.com/questions/69859/how-could-i-intercept-linux-sys-calls&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/426230/what-is-the-ld-preload-trick&quot;&gt;https://stackoverflow.com/questions/426230/what-is-the-ld-preload-trick&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.goldsborough.me/c/low-level/kernel/2016/08/29/16-48-53-the_-ld_preload-_trick/&quot;&gt;https://www.goldsborough.me/c/low-level/kernel/2016/08/29/16-48-53-the_-ld_preload-_trick/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/61668718/how-does-the-dynamic-linker-executes-proc-self-exe&quot;&gt;https://stackoverflow.com/questions/61668718/how-does-the-dynamic-linker-executes-proc-self-exe&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/NixOS/patchelf&quot;&gt;https://github.com/NixOS/patchelf&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/proot-me/proot&quot;&gt;https://github.com/proot-me/proot&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/skirsten/proot-portable-android-binaries?tab=readme-ov-file&quot;&gt;https://github.com/skirsten/proot-portable-android-binaries?tab=readme-ov-file&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/49085907/is-it-possible-to-change-the-value-of-proc-self-exe-for-an-execed-child-proc&quot;&gt;https://stackoverflow.com/questions/49085907/is-it-possible-to-change-the-value-of-proc-self-exe-for-an-execed-child-proc&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://man7.org/linux/man-pages/man2/PR_SET_MM_EXE_FILE.2const.html&quot;&gt;https://man7.org/linux/man-pages/man2/PR_SET_MM_EXE_FILE.2const.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/upx/upx/issues/249&quot;&gt;https://github.com/upx/upx/issues/249&lt;/a&gt; - /proc/self/exe can be unlinked by a different process…&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://lwn.net/Articles/920384/&quot;&gt;https://lwn.net/Articles/920384/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/68265901/can-i-change-proc-pid-exe-to-reflect-a-different-binary&quot;&gt;https://stackoverflow.com/questions/68265901/can-i-change-proc-pid-exe-to-reflect-a-different-binary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 16 Dec 2025 00:00:00 +0100</pubDate>
        <link>https://adangel.org/2025/12/16/glibc-trickery-part2/</link>
        <guid isPermaLink="true">https://adangel.org/2025/12/16/glibc-trickery-part2/</guid>
        
        <category>nodejs</category>
        
        <category>glibc</category>
        
        
      </item>
    
      <item>
        <title>Bicycle Tour 2025</title>
        <description>&lt;p&gt;This year’s tour went from &lt;strong&gt;From Munich via Claudia to Engadin&lt;/strong&gt;.
First leg goes from Munich to river Lech, then following the Lech until Füssen/Reutte and switching to
river Loisach. After the Fern Pass the last river is the Inn.&lt;/p&gt;

&lt;p&gt;It’s a bit similar to the &lt;a href=&quot;/content/bicycle-tour-2021&quot;&gt;bicycle tour of 2021&lt;/a&gt; but with a different starting
direction. Hoped to see Engadin with more sun, but the weather went worse unfortunately (even with
rain warnings in the region).&lt;/p&gt;

&lt;p&gt;You can see my tracks on the interactive map I created:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;/content/bicycle-tour-2025/&quot;&gt;Bicycle Tour 2025: From Munich via Claudia to Engadin&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;figure&gt;
    &lt;a href=&quot;/content/bicycle-tour-2025/&quot;&gt;
      &lt;img src=&quot;/assets/2025-10-15-bicycle-tour-2025/screenshot.png&quot; width=&quot;100%&quot; /&gt;
    &lt;/a&gt;
    &lt;figcaption&gt;Bicycle Tour 2025: From Munich via Claudia to Engadin&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I could now test my travel eSIM in Switzerland, as Switzerland is not part of the EU and therefore most
mobile carries only offer expensive roaming prices. I took &lt;a href=&quot;https://yesim.app/&quot;&gt;Yesim&lt;/a&gt; and after activating
the eSIM on the mobile phone (and also online in Yesim’s account), it worked quite good.
Interestingly, the public IP you get, is from Poland…&lt;/p&gt;

&lt;div class=&quot;images&quot;&gt;
















&lt;img src=&quot;/assets/2025-10-15-bicycle-tour-2025/what-is-my-ip.png&quot; width=&quot;200px&quot; /&gt;


















&lt;img src=&quot;/assets/2025-10-15-bicycle-tour-2025/ifconfig-co.png&quot; width=&quot;200px&quot; /&gt;


&lt;/div&gt;

&lt;p&gt;You can also see, that you won’t get a IPv6 address, which means, that IPv6 only services wouldn’t be
accessible.&lt;/p&gt;

&lt;p&gt;The fact, that the IP traffic is routed through Poland seems to be the normal case: Yesim is not acting as a
local Swiss mobile provider, but as some foreign mobile provider, which also roams in Switzerland. This has the
advantage, that it can roam through any available network with the best signal and is not limited to one
provider. It just happens to be the case, that Yesim has chosen a Polish provider. Other eSIM providers like
&lt;a href=&quot;https://esim.holafly.com&quot;&gt;Holafly&lt;/a&gt; route their traffic through China.&lt;/p&gt;

&lt;p&gt;See this news article &lt;a href=&quot;https://www.itnews.com.au/news/travel-esims-secretly-route-traffic-over-chinese-and-undisclosed-networks-study-619659&quot;&gt;Travel eSIMs secretly route traffic over Chinese and undisclosed networks: study&lt;/a&gt;
and the corresponding study &lt;a href=&quot;https://www.usenix.org/system/files/usenixsecurity25-motallebighomi.pdf&quot;&gt;eSIMplicity or eSIMplification? Privacy and
Security Risks in the eSIM Ecosystem&lt;/a&gt; and
the reddit post &lt;a href=&quot;https://www.reddit.com/r/eSIMs/comments/1mvdhtl/researchers_discover_esims_routing_traffic&quot;&gt;Researchers discover eSIMs routing traffic through China&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note, that this is only the public IP address, that is visible - how the traffic is actually internally routed,
is not visible. But your traffic appears to the visited sites with your public IP. And if there is some
geolocated restriction implemented on the sites, you might be surprised that these travel eSIMs act as if they
are a VPN. So, depending on what you want to use, you might also need an actual VPN provider to finally appear in
the country you are physically located… amazing. Something like NordVPN.&lt;/p&gt;

&lt;p&gt;The fact, that the traffic is routed through maybe far distant countries definitely affects the latency of
your connection - and most like also the throughput. For me, it was ok, I didn’t need much internet.
If I had used more, I probably would have noticed Polish ads and I think, I wouldn’t have been able to
by train tickets online for sbb (Schweizerische Bundesbahnen). But luckily, they still have ticket machines.&lt;/p&gt;
</description>
        <pubDate>Wed, 15 Oct 2025 00:00:00 +0200</pubDate>
        <link>https://adangel.org/2025/10/15/bicycle-tour-2025/</link>
        <guid isPermaLink="true">https://adangel.org/2025/10/15/bicycle-tour-2025/</guid>
        
        <category>gps</category>
        
        <category>gpx</category>
        
        <category>bicycle</category>
        
        
      </item>
    
      <item>
        <title>Using glibc trickery to run nodejs</title>
        <description>&lt;p&gt;Let’s assume, you need to run a recent nodejs binary on a very old linux system. More specifically, your
old linux system is a &lt;a href=&quot;https://en.wikipedia.org/wiki/SUSE_Linux_Enterprise&quot;&gt;SUSE Linux Enterprise&lt;/a&gt;. The old
version 12 is still supported, there is a service pack 5 available, even still this year. Version 12 was
released more than 10 years ago…&lt;/p&gt;

&lt;p&gt;What happens, if you try to run a recent, more or less statically linked, binary on such an old system?
You might experience the following error:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;node-v24.6.0-linux-x64/bin/node: /lib64/libm.so.6: version `GLIBC_2.27&apos; not found (required by node-v24.6.0-linux-x64/bin/node)
node-v24.6.0-linux-x64/bin/node: /lib64/libc.so.6: version `GLIBC_2.27&apos; not found (required by node-v24.6.0-linux-x64/bin/node)
node-v24.6.0-linux-x64/bin/node: /lib64/libc.so.6: version `GLIBC_2.28&apos; not found (required by node-v24.6.0-linux-x64/bin/node)
node-v24.6.0-linux-x64/bin/node: /lib64/libc.so.6: version `GLIBC_2.25&apos; not found (required by node-v24.6.0-linux-x64/bin/node)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;suse-linux-enterprise&quot;&gt;SUSE Linux Enterprise&lt;/h2&gt;

&lt;p&gt;This distribution is a commercial one, but it is based on a free distribution by SUSE: &lt;a href=&quot;https://en.opensuse.org/Portal:Leap&quot;&gt;openSUSE leap&lt;/a&gt;.
So, you can use openSUSE leap in the matching version to test and reproduce this yourself.&lt;/p&gt;

&lt;p&gt;According to the German wikipedia page &lt;a href=&quot;https://de.wikipedia.org/wiki/SUSE_Linux_Enterprise_Server&quot;&gt;SUSE Linux Enterprise Server&lt;/a&gt;
SLES 12 is based on openSUSE Leap 42. More specifically, SLES 12 SP3 is based on openSUSE Leap 42.3. For the most
recent service pack SLES 12 SP5, there doesn’t seem to be an openSUSE Leap equivalent version, but for our tests,
42.3 will do fine. Of course, you shouldn’t use 42.3 in production, as this won’t have any security fixes.&lt;/p&gt;

&lt;p&gt;The next generation of SLES (SUSE Linux Enterprise Server) is SLES 15, which is based on openSUSE Leap 15.
It’s convenient, that the version numbers now match. The latest version is SLES 15 SP 6 or openSUSE Leap 15.6.&lt;/p&gt;

&lt;p&gt;The openSUSE Leap distribution is available on docker.io: &lt;a href=&quot;https://hub.docker.com/r/opensuse/leap&quot;&gt;https://hub.docker.com/r/opensuse/leap&lt;/a&gt;. We can
simply start a docker image with the specific version:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;podman run -it --rm --pull newer -v $(pwd):/root/node-test docker.io/opensuse/leap:42 bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or the newer version:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;podman run -it --rm --pull newer -v $(pwd):/root/node-test docker.io/opensuse/leap:15 bash 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note: It bind-mounts the current directory into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/root/node-test&lt;/code&gt;, so that we can download nodejs
distributions there and run it later inside the container. These images are very basic and e.g.
don’t provide many packages, e.g. no “curl” or “wget”.&lt;/p&gt;

&lt;h2 id=&quot;how-to-reproduce&quot;&gt;How to reproduce&lt;/h2&gt;

&lt;p&gt;Let’s download the current version of nodejs from &lt;a href=&quot;https://nodejs.org/en/download/current&quot;&gt;https://nodejs.org/en/download/current&lt;/a&gt; as a
standalone binary and extract it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ mkdir node-test
$ cd node-test
$ curl -O https://nodejs.org/dist/v24.6.0/node-v24.6.0-linux-x64.tar.xz
$ tar xfJv node-v24.6.0-linux-x64.tar.xz
$ node-v24.6.0-linux-x64/bin/node --version
v24.6.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The last command runs nodejs and prints the version, you should see something like “v24.6.0”.&lt;/p&gt;

&lt;p&gt;Now, let’s run this inside Leap 42:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test docker.io/opensuse/leap:42 /root/node-test/node-v24.6.0-linux-x64/bin/node --version
/root/node-test/node-v24.6.0-linux-x64/bin/node: /lib64/libm.so.6: version `GLIBC_2.27&apos; not found (required by /root/node-test/node-v24.6.0-linux-x64/bin/node)
/root/node-test/node-v24.6.0-linux-x64/bin/node: /lib64/libc.so.6: version `GLIBC_2.27&apos; not found (required by /root/node-test/node-v24.6.0-linux-x64/bin/node)
/root/node-test/node-v24.6.0-linux-x64/bin/node: /lib64/libc.so.6: version `GLIBC_2.28&apos; not found (required by /root/node-test/node-v24.6.0-linux-x64/bin/node)
/root/node-test/node-v24.6.0-linux-x64/bin/node: /lib64/libc.so.6: version `GLIBC_2.25&apos; not found (required by /root/node-test/node-v24.6.0-linux-x64/bin/node)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And voila, you get the errors about “version GLIBC… not found” instead of the version number printed.&lt;/p&gt;

&lt;p&gt;However, it works inside Leap 15 (and therefore should work in SLES 15):&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test docker.io/opensuse/leap:15 /root/node-test/node-v24.6.0-linux-x64/bin/node --version
v24.6.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;libc-versions-of-suse-linux-enterprise&quot;&gt;Libc versions of SUSE Linux Enterprise&lt;/h2&gt;

&lt;p&gt;So, whats the difference? The “libc” version seems to be too old. Which version the system is running, you can
see by executing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ldd --version&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test docker.io/opensuse/leap:42 ldd --version
ldd (GNU libc) 2.22
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

$ podman run -it --rm --pull newer -v $(pwd):/root/node-test docker.io/opensuse/leap:15 ldd --version
ldd (GNU libc) 2.38
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, openSUSE Leap 42 (SLES 12) is built with and running with GNU libc 2.22.
The newer openSUSE Leap 15 (SLES 15) is instead built with and running with GNU libc 2.38.
And nodejs seems to require at least version 2.28.&lt;/p&gt;

&lt;h2 id=&quot;glibc-and-the-loader&quot;&gt;glibc and the loader&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Glibc&quot;&gt;GNU C library&lt;/a&gt; provides the basic functionality for all programs
running on Linux. This includes the kernel interface via syscalls. Even a simple hello world program
uses it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

int main() {
  printf(&quot;Hello world!\n&quot;);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you compile it, it is linked automatically against libc:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ gcc -o hello hello.c
$ ./hello
Hello world!
$ ldd hello
	linux-vdso.so.1 (0x00007f53b7d57000)
	libc.so.6 =&amp;gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007f53b7b2d000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f53b7d59000)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you statically link it, the you won’t see it anymore:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ gcc -static -o hello hello.c
$ ./hello 
Hello world!
$ ldd hello
	not a dynamic executable
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All the used methods are now embedded into the binary instead of dynamically linked in from libc.so.6.
This makes the binary bigger, but you don’t need to have the correct libc version available.&lt;/p&gt;

&lt;p&gt;When running a program under Linux, there are some environment variables, with which you can control from
where dynamic libraries are loaded. These are e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_LIBRARY_PATH&lt;/code&gt;. With that, you can kind of “override”
which dynamic/shared libraries are actually used. Usually, the shared libraries from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/lib&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/lib64&lt;/code&gt; are
used, but you might have a special program with special needs.
This is documented in the man page for &lt;a href=&quot;https://www.man7.org/linux/man-pages/man8/ld.so.8.html&quot;&gt;ld.so&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And there are other interesting options: E.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_PRELOAD&lt;/code&gt;, which allows you to basically override specific
methods without changing the original program. So you can inject or wrap specific system calls to e.g.
figure out where some resource leaks coming from.&lt;/p&gt;

&lt;p&gt;The man page &lt;a href=&quot;https://www.man7.org/linux/man-pages/man8/ld.so.8.html&quot;&gt;ld.so&lt;/a&gt; documents the loader, which is the part,
that loads a binary executable at the first place and the resolves the symbols finding and loading the shared objects.
And you can call it explicitly via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/lib64/ld-linux-x86-64.so.2 ...&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test docker.io/opensuse/leap:15 /lib64/ld-linux-x86-64.so.2 /root/node-test/node-v24.6.0-linux-x64/bin/node --version
v24.6.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’ll use this technique later on to create a wrapper script for nodejs…&lt;/p&gt;

&lt;h2 id=&quot;updating-glibc&quot;&gt;Updating glibc&lt;/h2&gt;

&lt;p&gt;When a distribution updates to a new glibc version, this usually means that every package needs to be rebuilt,
which is a huge effort and is risky. That’s why such updates are usually done in major versions (e.g. SLES 12 vs. SLES 15).&lt;/p&gt;

&lt;p&gt;But maybe we can use the glibc version from SLES 15 (Leap 15) back in SLES 12 (Leap 42) and force nodejs to use
it via the environment variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_LIBRARY_PATH&lt;/code&gt;. Let’s try.&lt;/p&gt;

&lt;p&gt;First step is, to find the package repository of Leap 15, so that we can download the glibc rpm package from there:
The repositories urls are listed on the page &lt;a href=&quot;https://en.opensuse.org/Package_repositories&quot;&gt;Package repositories&lt;/a&gt;
and the URL for Leap 15.6 is: &lt;a href=&quot;https://download.opensuse.org/distribution/leap/15.6/repo/oss/&quot;&gt;https://download.opensuse.org/distribution/leap/15.6/repo/oss/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We actually need &lt;a href=&quot;https://download.opensuse.org/distribution/leap/15.6/repo/oss/x86_64/glibc-2.38-150600.12.1.x86_64.rpm&quot;&gt;glibc-2.38-150600.12.1.x86_64.rpm&lt;/a&gt;.
This saves us from compiling it ourselves. Hopefully, this new version will somehow run on the old Leap 42 environment.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -LO https://download.opensuse.org/distribution/leap/15.6/repo/oss/x86_64/glibc-2.38-150600.12.1.x86_64.rpm
$ rpm2cpio glibc-2.38-150600.12.1.x86_64.rpm
$ cpio -div -D glibc-2.38 &amp;lt; glibc-2.38-150600.12.1.x86_64.rpm.tar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we have it extracted to the local directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./glibc-2.38/lib64/libc.so.6&lt;/code&gt; (and many more files).&lt;/p&gt;

&lt;p&gt;Using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_LIBRARY_PATH&lt;/code&gt; like so, doesn’t work:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test \
    -e LD_LIBRARY_PATH=/root/node-test/glibc-2.38/lib64 \
    docker.io/opensuse/leap:42 /root/node-test/node-v24.6.0-linux-x64/bin/node --version
/root/node-test/node-v24.6.0-linux-x64/bin/node: relocation error: /root/node-test/glibc-2.38/lib64/libc.so.6: symbol _dl_signal_error, version GLIBC_PRIVATE not defined in file ld-linux-x86-64.so.2 with link time reference
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s try with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_PRELOAD&lt;/code&gt;, as it is described on &lt;a href=&quot;https://cylab.be/blog/388/a-solution-to-version-glibc-2xx-not-found&quot;&gt;A Solution to Version GLIBC_2.XX Not Found&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test \
    -e LD_PRELOAD=/root/node-test/glibc-2.38/lib64/libc.so.6 \
    docker.io/opensuse/leap:42 /root/node-test/node-v24.6.0-linux-x64/bin/node --version
/root/node-test/node-v24.6.0-linux-x64/bin/node: /lib64/libm.so.6: version `GLIBC_2.27&apos; not found (required by /root/node-test/node-v24.6.0-linux-x64/bin/node)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Almost, let’s try the combination:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test \
    -e LD_PRELOAD=/root/node-test/glibc-2.38/lib64/libc.so.6 \
    -e LD_LIBRARY_PATH=/root/node-test/glibc-2.38/lib64 \
    docker.io/opensuse/leap:42 /root/node-test/node-v24.6.0-linux-x64/bin/node --version
/root/node-test/node-v24.6.0-linux-x64/bin/node: relocation error: /root/node-test/glibc-2.38/lib64/libc.so.6: symbol _dl_signal_error, version GLIBC_PRIVATE not defined in file ld-linux-x86-64.so.2 with link time reference
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;No. But maybe if we preload both libc and libm?&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test \
    -e LD_PRELOAD=/root/node-test/glibc-2.38/lib64/libc.so.6:/root/node-test/glibc-2.38/lib64/libm.so.6 \
    docker.io/opensuse/leap:42 /root/node-test/node-v24.6.0-linux-x64/bin/node --version
/root/node-test/node-v24.6.0-linux-x64/bin/node: relocation error: /root/node-test/glibc-2.38/lib64/libc.so.6: symbol _dl_signal_error, version GLIBC_PRIVATE not defined in file ld-linux-x86-64.so.2 with link time reference
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Maybe libc and libm and additionally LD_LIBRARY_PATH?&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test \
    -e LD_PRELOAD=/root/node-test/glibc-2.38/lib64/libc.so.6:/root/node-test/glibc-2.38/lib64/libm.so.6 \
    -e LD_LIBRARY_PATH=/root/node-test/glibc-2.38/lib64 \
    docker.io/opensuse/leap:42 /root/node-test/node-v24.6.0-linux-x64/bin/node --version
/root/node-test/node-v24.6.0-linux-x64/bin/node: relocation error: /root/node-test/glibc-2.38/lib64/libc.so.6: symbol _dl_signal_error, version GLIBC_PRIVATE not defined in file ld-linux-x86-64.so.2 with link time reference
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;No luck. It complains about a missing symbol in the loader &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ld-linux-x86-64.so.2&lt;/code&gt; itself.&lt;/p&gt;

&lt;p&gt;Now it’s time to also replace the loader and calling the loader explicitly:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test \
    -e LD_LIBRARY_PATH=/root/node-test/glibc-2.38/lib64 \
    docker.io/opensuse/leap:42 \
    /root/node-test/glibc-2.38/lib64/ld-linux-x86-64.so.2 \
    /root/node-test/node-v24.6.0-linux-x64/bin/node --version
v24.6.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There we go! It is important to have both the loader and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_LIBRARY_PATH&lt;/code&gt; pointing to the new libc version.
Otherwise it won’t work.&lt;/p&gt;

&lt;h2 id=&quot;the-wrapper-script&quot;&gt;The wrapper script&lt;/h2&gt;

&lt;p&gt;In order to make it simpler to call nodejs without always going through the loader explicitly, we’ll create
a small bash script to run it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/bash
NODE_HOME=/root/node-test/node-v24.6.0-linux-x64
GLIBC_HOME=/root/node-test/glibc-2.38

exec ${GLIBC_HOME}/lib64/ld-linux-x86-64.so.2 \
  --library-path ${GLIBC_HOME}/lib64 \
  ${NODE_HOME}/bin/node &quot;$@&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Save this as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/root/node-test/node&lt;/code&gt; and make it executable.&lt;/p&gt;

&lt;p&gt;Then, you can run nodejs as follows:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test \
    docker.io/opensuse/leap:42 \
    /root/node-test/node --version
v24.6.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Instead of calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/root/node-test/node-v24.6.0-linux-x64/bin/node&lt;/code&gt; directly, you are simply
executing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/root/node-test/node&lt;/code&gt;, which takes care of the details. If you put that onto your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt;, it’s
even simpler:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test \
    -e PATH=/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin \
    docker.io/opensuse/leap:42 \
    node --version
v24.6.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And you can even run npm:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ podman run -it --rm --pull newer -v $(pwd):/root/node-test \
    -e PATH=/root/node-test:/root/node-test/node-v24.6.0-linux-x64/bin:/bin:/usr/bin \
    docker.io/opensuse/leap:42 \
    npm --version
11.5.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm&lt;/code&gt; executable is found in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt; under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/root/node-test/node-v24.6.0-linux-x64/bin/npm&lt;/code&gt;. This
is actually a shebang script to run “node” from the PATH. Since the wrapper script &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/root/node-test/node&lt;/code&gt; exists,
this will be used to run node, which in turn runs the npm js script.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;You can replace the dynamically loaded shared objects with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_LIBRARY_PATH&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_PRELOAD&lt;/code&gt;. But sometimes,
this is not enough - you need to replace the dynamic loader as well.&lt;/p&gt;

&lt;p&gt;With this trick (executing the loader &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/lib64/ld-linux-x86-64.so.2&lt;/code&gt; explicitly), it was possible to
run a recent nodejs version on an old linux distribution.&lt;/p&gt;

&lt;p&gt;There are some caveats: As the libc is a wrapper around syscalls and the syscalls are provided by the running
kernel version, this could be a problem: If nodejs uses a new libc function that is implemented using a new
syscall, that is not available in the old kernel, this won’t work. However, this seems not to be the case.&lt;/p&gt;

&lt;p&gt;Using this trick is therefore not recommended to use in production environments. Having said that, it is
still fascinating, that this even is possible and works.&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Glibc&quot;&gt;https://en.wikipedia.org/wiki/Glibc&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://cylab.be/blog/388/a-solution-to-version-glibc-2xx-not-found&quot;&gt;https://cylab.be/blog/388/a-solution-to-version-glibc-2xx-not-found&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/opensuse/leap/tags&quot;&gt;https://hub.docker.com/r/opensuse/leap/tags&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://de.wikipedia.org/wiki/SUSE_Linux_Enterprise_Server&quot;&gt;https://de.wikipedia.org/wiki/SUSE_Linux_Enterprise_Server&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.opensuse.org/openSUSE:Roadmap&quot;&gt;https://en.opensuse.org/openSUSE:Roadmap&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.man7.org/linux/man-pages/man8/ld.so.8.html&quot;&gt;https://www.man7.org/linux/man-pages/man8/ld.so.8.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://samanbarghi.com/post/2014-09-05-how-to-wrap-a-system-call-libc-function-in-linux/&quot;&gt;https://samanbarghi.com/post/2014-09-05-how-to-wrap-a-system-call-libc-function-in-linux/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.goldsborough.me/c/low-level/kernel/2016/08/29/16-48-53-the_-ld_preload-_trick/&quot;&gt;https://www.goldsborough.me/c/low-level/kernel/2016/08/29/16-48-53-the_-ld_preload-_trick/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sat, 16 Aug 2025 00:00:00 +0200</pubDate>
        <link>https://adangel.org/2025/08/16/glibc-trickery/</link>
        <guid isPermaLink="true">https://adangel.org/2025/08/16/glibc-trickery/</guid>
        
        <category>nodejs</category>
        
        <category>glibc</category>
        
        
      </item>
    
      <item>
        <title>Local WordPress copy with docker compose</title>
        <description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;When you have a &lt;a href=&quot;https://wordpress.org/download/&quot;&gt;WordPress&lt;/a&gt; page and you want to test something without
testing in production… what do you need?&lt;/p&gt;

&lt;p&gt;A separate testing stage. Using docker, it is simple to start up some LAMPish (linux, apache, mysql, php) stack
locally and start testing without the fear to break something.&lt;/p&gt;

&lt;h2 id=&quot;docker-compose&quot;&gt;Docker Compose&lt;/h2&gt;

&lt;p&gt;There are kind of official docker images for WordPress available on docker hub, e.g.
&lt;a href=&quot;https://hub.docker.com/_/wordpress&quot;&gt;https://hub.docker.com/_/wordpress&lt;/a&gt;. In order to use it, you also need a database, e.g.
&lt;a href=&quot;https://hub.docker.com/_/mysql&quot;&gt;https://hub.docker.com/_/mysql&lt;/a&gt;. For easy access to the database, we’ll use another image:
&lt;a href=&quot;https://hub.docker.com/_/phpmyadmin&quot;&gt;https://hub.docker.com/_/phpmyadmin&lt;/a&gt;. The complete docker compose file looks like this:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;wordpress&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;wordpress&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8080:80&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;WORDPRESS_DB_HOST&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;db&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;WORDPRESS_DB_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exampleuser&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;WORDPRESS_DB_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;examplepass&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;WORDPRESS_DB_NAME&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exampledb&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;WORDPRESS_TABLE_PREFIX&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;prefix_&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./wordpress:/var/www/html&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mysql:8.0&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exampledb&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;MYSQL_USER&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;exampleuser&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;examplepass&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;MYSQL_RANDOM_ROOT_PASSWORD&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;1&apos;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./db:/var/lib/mysql&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;phpmyadmin&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;phpmyadmin&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;8081:80&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PMA_ARBITRARY=1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Save this as a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now you can start all three services with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose up&lt;/code&gt;.
The WordPress installation will be available at &lt;a href=&quot;http://localhost:8080&quot;&gt;http://localhost:8080&lt;/a&gt;
and the phpmyadmin is available at &lt;a href=&quot;http://localhost:8081&quot;&gt;http://localhost:8081&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are some important configuration options to know: The service &lt;em&gt;wordpress&lt;/em&gt; uses bind mounts
to access the local directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./wordpress&lt;/code&gt; inside the container. When the container starts up the first time,
WordPress is extracted and installed into this directory - and this directory is then served by the
included apache webserver. If you start it the first time, you are presented with the usual WordPress
installation wizard where you need to setup a user and password.
If the container is restarted, this directory is reused. So the data
is preserved. This is then also a simple way, to replace this default WordPress installation
with your production copy, see below.&lt;/p&gt;

&lt;p&gt;Similar thing is happening with the data of the mysql database: This is written down into the bind
mounted local directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./db&lt;/code&gt; and is preserved through restarts.&lt;/p&gt;

&lt;p&gt;phpmyadmin doesn’t use any volume, it simply serves the application. As it is included in the same
docker compose file, it can directly access the database via the service name “db”.&lt;/p&gt;

&lt;p&gt;There are some configuration options, which are provided as environment variables. E.g. the WordPress
instance needs to know the database, which must match the values for the mysql service. For phpmyadmin
we use “PMA_ARBITRARY=1” which allows to use this phpmyadmin instance to connect to any reachable
mysql server. You can simply enter “db” as server name and use “exampleuser/examplepass” to login.&lt;/p&gt;

&lt;p&gt;You can stop the services with &lt;kbd&gt;Ctrl+C&lt;/kbd&gt;, as docker compose up runs in the foreground.
Start them again with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose up&lt;/code&gt; or destroy the with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose down&lt;/code&gt;.
Note: Only the containers are destroyed, not the data, that is preserved in the local directories.&lt;/p&gt;

&lt;h2 id=&quot;copying-the-data&quot;&gt;Copying the data&lt;/h2&gt;

&lt;p&gt;A WordPress instance consists of the PHP files itself (including any additionally installed plugin),
the uploaded files and the content of the pages which is stored in the database. In order to replicate
a production instance locally, you need to copy all.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Stop the (docker compose) services if they are still running.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Notice the userid/groupid ownership of the files. You’ll need this later.
Then delete or move away all files in the local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./wordpress&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Copy all files from the production instance into local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./wordpress&lt;/code&gt;. And adjust the file ownership
accordingly, e.g. with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chown -R 100032:100032 ./wordpress&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Create a database dump from the production instance, e.g. via phpymadmin’s export functionality.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Adjust the WordPress configuration for the local stage, that means:
Edit the file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./wordpress/wp-config.php&lt;/code&gt; and change the following properties:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;define( &apos;DB_NAME&apos;, &apos;exampledb&apos; );
define( &apos;DB_USER&apos;, &apos;exampleuser&apos; );
define( &apos;DB_PASSWORD&apos;, &apos;examplepass&apos; );
define( &apos;DB_HOST&apos;, &apos;db&apos; );

// https://stackoverflow.com/questions/10578369/wordpress-wp-admin-redirects-to-https
// allow to use wp-admin locally
define( &apos;FORCE_SSL_ADMIN&apos;, false );
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Then start the services with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose up&lt;/code&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Go to phpyadmin: &lt;a href=&quot;http://localhost:8081&quot;&gt;http://localhost:8081&lt;/a&gt; and import your production database dump into database “exampledb”.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Go to the table “options” (it might be prefixed with some table prefix) and change the options “siteurl” and “home”
to be “http://localhost:8080”.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Then go to WordPress: &lt;a href=&quot;http://localhost:8080&quot;&gt;http://localhost:8080&lt;/a&gt;. It should display the production page.
You can also log in to wp-admin: &lt;a href=&quot;http://localhost:8080/wp-admin&quot;&gt;http://localhost:8080/wp-admin&lt;/a&gt; using the production credentials.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Update WordPress, install new plugins, test. This should all work now locally.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note: Some themes might have stored the production URLs for images, so keep an eye on the network requests
to see, if all requests really end up on localhost. E.g. I noticed that the configured background image
for “Avant Portfolio” was using a absolute production url. But configuring the background image again
solved the issue.&lt;/p&gt;

&lt;p&gt;Note 2: Maybe you have some absolute links in use for some custom navigation. You’ll need to change
those manually to point to “http://localhost:8080/…” (or just use relative links if possible).&lt;/p&gt;

&lt;p&gt;Note 3: If the file permissions are messed up, WordPress updates or plugin updates / installations
might not work. So be sure, to use the correct userid.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;This article shows an easy way to get a production copy of a WordPress instance running locally.
You can simply copy the local folders and create another WordPress instance locally for another test.
And all this without directly impacting the production instance.&lt;/p&gt;
</description>
        <pubDate>Sun, 29 Jun 2025 00:00:00 +0200</pubDate>
        <link>https://adangel.org/2025/06/29/wordpress-docker-compose/</link>
        <guid isPermaLink="true">https://adangel.org/2025/06/29/wordpress-docker-compose/</guid>
        
        <category>wordpress</category>
        
        <category>docker</category>
        
        <category>php</category>
        
        
      </item>
    
      <item>
        <title>Casio Wave Ceptor 3354 WVA-430TE battery replacement</title>
        <description>&lt;p&gt;I have this wristwatch, which is also viewable in the online watch museum:
&lt;a href=&quot;https://uhrerbe.com/unt/14483-casio_wave_ceptor_3354_wva___430te_funkuhr_tough_solar_titanium_herren_armbanduhr.html&quot;&gt;Casio Wave Ceptor 3354 Wva - 430te Funkuhr Tough Solar Titanium Herren Armbanduhr&lt;/a&gt;.
They have much better pictures than I have.&lt;/p&gt;

&lt;p&gt;It is a solar watch that didn’t need any maintenance for many years. I needed a new watchstrap once. But since
one or two years, every now and then the watch stopped. After a while, it started to work again; I just had to adjust
the hands because it stopped in the middle. And after it started to move again, it didn’t display the
correct analog time - while the digital time was already good. But that is possible with this clock - it’s even
described in the manual under “Adjust analog time”.&lt;/p&gt;

&lt;p&gt;This happened more often lately. It’s winter and cold (not good for a chemically based battery) and my sleeves
usually cover the clock almost all the day. So it probably just ran out of power from the rechargeable battery - until
it stopped. It couldn’t even go to save mode properly (moving the hands to 12:00). It just stopped.&lt;/p&gt;

&lt;p&gt;The watch has a small solar panel, which is usually sufficient to charge the battery if enough sun light hits the
surface during the day. And I had no problems at all for many years.&lt;/p&gt;

&lt;p&gt;But now, after about 17 years in constant use, it’s time for a new battery it seems. Kudos to Casio, which made
this easy repairable. The backplane uses 4 screws and then you are in. You replace the battery, there is
a sign to use only the correct battery type - which I confirmed, I ordered the correct one: CTL1616.
When putting in the new battery, the watch showed the same behavior as the before: It didn’t run, it was
in some kind of emergency power save mode. The one indicator bottom left in the display was blinking, but
no other signs were shown. If you short-circuit point AC with ground for two seconds - as noted
on the sticker - it started to run. This behavior indicates, that the theory of a worn out battery after 17 years
could be correct. I measured the voltage of the old battery - it showed about 2.4V. But so did the new battery.
I guess, voltage on its own is just not a helpful indicator of the health of a rechargeable battery.&lt;/p&gt;

&lt;p&gt;Anyway, I switched the battery now. Let’s see, how long this one lasts.&lt;/p&gt;

&lt;p&gt;Here are some pictures:&lt;/p&gt;

&lt;div class=&quot;images&quot;&gt;
















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-04-28-casio-wave-ceptor-3354-wva-430te-battery-replacement/casio-1.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;watch - front side&lt;/figcaption&gt;
&lt;/figure&gt;


















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-04-28-casio-wave-ceptor-3354-wva-430te-battery-replacement/casio-2.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;watch - back side&lt;/figcaption&gt;
&lt;/figure&gt;


















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-04-28-casio-wave-ceptor-3354-wva-430te-battery-replacement/casio-3.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;inside of watch: sticker on the battery&lt;/figcaption&gt;
&lt;/figure&gt;


















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-04-28-casio-wave-ceptor-3354-wva-430te-battery-replacement/casio-4.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;inside of watch: sticker removed, battery visible&lt;/figcaption&gt;
&lt;/figure&gt;


















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-04-28-casio-wave-ceptor-3354-wva-430te-battery-replacement/casio-5.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;the new battery - looks like the correct type CTL1616 for casio&lt;/figcaption&gt;
&lt;/figure&gt;


















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-04-28-casio-wave-ceptor-3354-wva-430te-battery-replacement/casio-6.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;new battery left, old battery right: both are even from Panasonic&lt;/figcaption&gt;
&lt;/figure&gt;


















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-04-28-casio-wave-ceptor-3354-wva-430te-battery-replacement/casio-7.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;the old battery had an additional insulation which I reused&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;/div&gt;

&lt;p&gt;&lt;br clear=&quot;all&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 28 Apr 2025 00:00:00 +0200</pubDate>
        <link>https://adangel.org/2025/04/28/casio-wave-ceptor-3354-wva-430te-battery-replacement/</link>
        <guid isPermaLink="true">https://adangel.org/2025/04/28/casio-wave-ceptor-3354-wva-430te-battery-replacement/</guid>
        
        <category>repair</category>
        
        
      </item>
    
      <item>
        <title>Glitch in the matrix?</title>
        <description>&lt;p&gt;Just to manage expectations - it is probably explainable, how such things happen in the real world.
But it’s nevertheless something strange, if you take signposts seriously.&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/berg-am-laim-underpass.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;Berg am Laim&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So, first things first. Let me explain the situation: In Munich there is an underpass in Berg am Laim, that
goes below the railways. In the last year or so, this underpass has been a bit refurbished and new signs
have been installed.&lt;/p&gt;

&lt;p&gt;The signs that have been added show the shortest exit way. That’s probably a new regulation that such signs
need to be installed and glowing if the underpass has a certain length and could be considered a tunnel.
This underpass is probably the gray area, as you can see the light at the end of the tunnel already. So it
is not very long.&lt;/p&gt;

&lt;div class=&quot;images&quot;&gt;
















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/underpass-sign.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;Underpass... (100m before)&lt;/figcaption&gt;
&lt;/figure&gt;


















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/tunnel-sign.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;or tunnel?&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;br clear=&quot;all&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;Anyway, here is the sign at the beginning of the tunnel:&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-1.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;Sign 1&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As you can see, it has been added after 20 meters and you can exit the tunnel to either side: The nearest exit
would be 20 meters away, the other end is 150 meters away. That allows you to deduce that the total
length of the underpass is 170 meters. So far, so good.&lt;/p&gt;

&lt;p&gt;After another 30 meters, you see the next sign:&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-2.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;Sign 2&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We see, it says either 50 meters or 120 meters - in total 170 meters. Still makes sense, no problem so far.&lt;/p&gt;

&lt;p&gt;But wait, until we walked another 20 meters to the next sign:&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-3.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;Sign 3&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Here we can clearly read, that the exit is either 70 meters or 90 meters away. In total our tunnel
is now 160 meters long. &lt;strong&gt;It shrunk 10 meters.&lt;/strong&gt; Interesting. Between the last sign and this one
the tunnel lost 10 meters. Good for us, who want to go to the other side, we need to walk 10 meters less.&lt;/p&gt;

&lt;p&gt;Let’s see, how it goes on. After another 20 meters, we see the next sign:&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-4.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;Sign 4&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It says 90/70 meters, which is in total still 160 meters. As we see from the numbers, we passed already
more than half of the tunnel. The far end is now nearer.&lt;/p&gt;

&lt;p&gt;Let’s walk another 30 meters to the next sign. But wait, what does it show?&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-5.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;Sign 5&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It says 120/50 meters, in total it’s again 170 meters. It seems we left the weird area, when we were
pleased already that we need to walk 10 meters less. &lt;strong&gt;The tunnel is back to its original length.&lt;/strong&gt;
That’s very strange.&lt;/p&gt;

&lt;p&gt;Just to be sure, let’s walk to the last sign, 30 more meters:&lt;/p&gt;

&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-6.jpg&quot; width=&quot;200px&quot; /&gt;
    &lt;figcaption&gt;Sign 6&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It says logically 150/20 meters, in total 170 meters. All makes sense.&lt;/p&gt;

&lt;p&gt;Just to double check, let’s walk in the same direction but on the other side of the street and observe
the exit signs there:&lt;/p&gt;

&lt;div class=&quot;images&quot;&gt;
















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-1r.jpg&quot; width=&quot;150px&quot; /&gt;
    &lt;figcaption&gt;Sign Right 1: 150m&lt;/figcaption&gt;
&lt;/figure&gt;


















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-2r.jpg&quot; width=&quot;150px&quot; /&gt;
    &lt;figcaption&gt;Sign Right 2: 150m&lt;/figcaption&gt;
&lt;/figure&gt;


















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-3r.jpg&quot; width=&quot;150px&quot; /&gt;
    &lt;figcaption&gt;Sign Right 3: 160m&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;br clear=&quot;all&quot; /&gt;
















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-4r.jpg&quot; width=&quot;150px&quot; /&gt;
    &lt;figcaption&gt;Sign Right 4: 160m&lt;/figcaption&gt;
&lt;/figure&gt;


















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-5r.jpg&quot; width=&quot;150px&quot; /&gt;
    &lt;figcaption&gt;Sign Right 5: 160m&lt;/figcaption&gt;
&lt;/figure&gt;


















&lt;figure&gt;
    &lt;img class=&quot;image&quot; src=&quot;/assets/2025-03-23-glitch-in-the-matrix/exit-sign-6r.jpg&quot; width=&quot;150px&quot; /&gt;
    &lt;figcaption&gt;Sign Right 6: 160m&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;br clear=&quot;all&quot; /&gt;
&lt;/div&gt;

&lt;p&gt;The right side seems to be more consistent: It just expands 10m from one end to the other.
Interestingly, the tunnel seems to be 10m longer on the left side than on the right side.&lt;/p&gt;

&lt;p&gt;Another look at this right side of the tunnel reveals another strange thing: The pictures are
taken in order, so you should expect, that the distance from one end (where we started) is
strictly monotonically increasing, while the distance to the other end (where we are heading to) is
strictly monotonically decreasing. The figure on the sign should always look into the direction
of the nearest exit. The distance to the starting end of the tunnel is: 10m, 40m, then suddenly
90m, then back to 70m (I definitely didn’t walk backwards…) and then jumping up to 120m.
Something strange is happening here.&lt;/p&gt;

&lt;p&gt;In summary, this tunnel is a very interesting construction: If we use this effect on purpose
for other tunnels or maybe even streets, we could save so much time. Just shorten the
distance in the middle of a route and we’ll be faster arriving at the destination. Nice.&lt;/p&gt;

&lt;p&gt;There’s a catch though: If you travel in reverse direction, the length of the tunnel might
be longer. Which gives the routing algorithm another way to optimize routes.&lt;/p&gt;
</description>
        <pubDate>Sun, 23 Mar 2025 00:00:00 +0100</pubDate>
        <link>https://adangel.org/2025/03/23/glitch-in-the-matrix/</link>
        <guid isPermaLink="true">https://adangel.org/2025/03/23/glitch-in-the-matrix/</guid>
        
        <category>misc</category>
        
        
      </item>
    
      <item>
        <title>Java by Comparison and PMD</title>
        <description>&lt;p&gt;This is now a different kind of blog post. I want to present a little bit the book “Java by Comparison” by
Simon Harrer, Jörg Lenhard, and Linus Dietz. But not just present it, instead evaluate, which examples
could be automated to be detected by static code analysis and maybe which PMD rules already exist.&lt;/p&gt;

&lt;h2 id=&quot;the-book&quot;&gt;The book&lt;/h2&gt;

&lt;p&gt;The book itself is not new, it is from 2018. It contains 70 examples, which are presented in a concise form:
The left page shows the example with the “bad” code and the right page shows the improved code. So you
can see the example side-by-side. Additionally, there are lots of explanations of course.&lt;/p&gt;

&lt;p&gt;I like the approach taken, to explain common programming “rules” and best practices with examples. That
is much more catchy than just a written down explanation.&lt;/p&gt;

&lt;p&gt;The book is available from the &lt;a href=&quot;https://pragprog.com/&quot;&gt;Pragmatic Bookshelf&lt;/a&gt; and has an own
page: &lt;a href=&quot;https://pragprog.com/titles/javacomp/java-by-comparison/&quot;&gt;https://pragprog.com/titles/javacomp/java-by-comparison/&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;the-structure&quot;&gt;The structure&lt;/h2&gt;

&lt;p&gt;Organized by wider topics. I’ll follow the same structure. Maybe sometimes I’ll combine two examples.&lt;/p&gt;

&lt;h3 id=&quot;small-code-cleanups&quot;&gt;Small code cleanups&lt;/h3&gt;

&lt;p&gt;This first chapter is about small code style cleanups, like superfluous code or inconvenient written code, that
is hard to read.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Unnecessary Comparisons” are conditional expressions, that are compared against a boolean, such
as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if (a == true) ...&lt;/code&gt;. This can be written more concise as just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if (a)&lt;/code&gt;, no need for this extra comparison.
It turns out, there is already a PMD rule for that: &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#simplifybooleanexpressions&quot;&gt;SimplifyBooleanExpressions&lt;/a&gt;. The reasoning behind this is: readability and code clutter.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Negations” makes the point, that an expression is easier to read and understand, if there is no negation
involved. E.g. instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if (!isEmpty())&lt;/code&gt; use better &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if (isNotEmpty())&lt;/code&gt;. There is no direct PMD rule that finds
such code, but it could be easily written as an XPath expression. E.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;//UnaryExpression[@Operator = &apos;!&apos;]&lt;/code&gt;.
There is however a related rule, that is &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#confusingternary&quot;&gt;ConfusingTernary&lt;/a&gt;.
This makes the point, if you have an if-else-expression and you use a negated condition, just swap them around
and use a positive condition.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Return Boolean Expressions Directly” is about avoiding clutter again. Instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return true&lt;/code&gt; inside a
if condition, you can directly return the condition and avoid the if statement entirely. There is already a
PMD rule: &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#simplifybooleanreturns&quot;&gt;SimplifyBooleanReturns&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Simplify Boolean Expressions” is about readability and easier understanding. There is a PMD rule with the same
name, but that rule does something different, see above. If the boolean expression is a bit more complicated,
it makes sense to introduce some local variables to give the parts of the expression a name and make it easier
to comprehend. This has currently no equivalent PMD rule.
However, we could count the number of parts the expression consists of (literally how many &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;||&lt;/code&gt; we have).
The suggestion would be to split this up and introduce named local
variables or methods. It would be similar like
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#cyclomaticcomplexity&quot;&gt;CyclomaticComplexity&lt;/a&gt;
but focused on a single expression.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid NullPointerException in Conditionals” talks about the issue of forgetting to check the input parameters
of public (or protected or default) methods against &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; before using them. There are some related PMD rules,
that help to avoid such NPEs:
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#literalsfirstincomparisons&quot;&gt;LiteralsFirstInComparisons&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#brokennullcheck&quot;&gt;BrokenNullCheck&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#equalsnull&quot;&gt;EqualsNull&lt;/a&gt; (which is a bit silly),
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#misplacednullcheck&quot;&gt;MisplacedNullCheck&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#unusednullcheckinequals&quot;&gt;UnusedNullCheckInEquals&lt;/a&gt;.
But there is no rule that e.g. checks whether a method’s parameter is used without a null check. This rule
seems to be possible, but might yield a lot of false positives. It might be a code style rule, as the rule
of thumb would be then: always check parameters for null before using them.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Switch Fallthrough” is self-explanatory. And there is already a PMD rule available:
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#implicitswitchfallthrough&quot;&gt;ImplicitSwitchFallThrough&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Always Use Braces” is a code-style topic. PMD used to have multiple rules for each statement that
checked, whether braces are used. Since PMD 6.2.0, there is one rule for that:
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#controlstatementbraces&quot;&gt;ControlStatementBraces&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Ensure Code Symmetry” is an interesting one. This is about readability and understandability of the code.
The examples shows a if-else-if structure, where some branches do deny access and other branches grant access.
In order to make the code more symmetrical, these two different concerns should be split up and separated, so
that when reading the code, one has to understand only one part at a time and not both concerns at once.
Detecting such an asymmetry sounds not so easy. We could use some heuristics like (in case of if statements)
the number of statements in each branch or the accessed variables in each branch. It’s definitively a
rule, that would only give a hint, that something could be improved in the code, but there are no
hard indicators.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;improved-code-style&quot;&gt;Improved code style&lt;/h3&gt;

&lt;p&gt;The next chapter is about even better code style.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;“Replace Magic Numbers with Constants” is a simple rule that helps in understanding the code. If you use
only literals for the numbers, that you would need to explain the meaning of the number. This can be improved
by defining a constant with a name for the number and use the constant. Now the number has a name and a meaning
and the code is easier to understand. Surprisingly, PMD doesn’t have such a rule. I know however, that
Checkstyle has one, e.g. &lt;a href=&quot;https://checkstyle.org/checks/coding/magicnumber.html#MagicNumber&quot;&gt;MagicNumber&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor Enums Over Integer Constants” is a follow-up on the previous example - enums might even be
better in some circumstances. There is no rule in PMD for that. The rule would need to be a bit more
intelligent than just pointing to constants (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static final&lt;/code&gt;) - it would need to identify the usages
of these constants. In the example in the book, there is a method which takes an input parameter and
compares this against a bunch of constants and only doing something, if the input parameter matches
one of the constants. This essentially limits the possible values for the input parameter by using
the constants, which makes it a perfect case for enums. That means, a PMD rule would need to identify
such a method, which probably would only work reliably in simple cases such as the given example.
It could look a bit broader and consider the cohesion between constants and methods and if there is
a single method where a bunch of constants are used (and the constants are not used somewhere else), then
we could suggest to introduce an enum. All this would require, that the constants are not public, as
otherwise we would need to check the usages across the whole project.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor For-Each Over For Loops” is an easy one and there is a PMD rule:
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#forloopcanbeforeach&quot;&gt;ForLoopCanBeForeach&lt;/a&gt;.
The rule description however misses an explanation, why a foreach loop should be used instead. The reason
is, that the code is simpler, because it doesn’t expose unnecessary variables such as the index variable.
That makes the code less prone to mistakes (e.g. using the wrong variable for accessing the element by index, etc.).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Collection Modification During Iteration” finds calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add&lt;/code&gt; or any other modification
of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java.util.Collection&lt;/code&gt;, while it is iterated over in a foreach loop. In most implementations this will
lead to a “ConcurrentModificationException”. In case we need to remove an element during iteration, we
should use the Iterator explicitly, which has a remove-method (given this operation is actually supported).
Another way would be, to iterate over a copy of the collection, so that we can modify the original collection.
PMD doesn’t have such a rule, but it should be easy to implement this. It would be in the category “error prone”.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Compute-Intense Operations During Iteration” belongs to the category “performance”. In the example, a
call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Pattern.matches&lt;/code&gt; is done inside a loop over and over again, always with the same regex pattern. The method
internally compiles the regex and this is an “expensive” operation, which should be done only once. The resulting
pattern should be reused. There are other methods around regex, that suffer from the same “not cached pattern”: e.g.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;String.replaceAll(String, String)&lt;/code&gt; - if this is called within a loop with the first argument always being
the same, the same pattern would be compiled over and over again. There are probably more cases of “expensive”
operation, that should be avoided inside a loop. For the Apex language, there is the rule
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_apex_performance.html#operationwithhighcostinloop&quot;&gt;OperationWithHighCostInLoop&lt;/a&gt;,
which serves a similar purpose (but finds other method calls). For Java, we have already
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_performance.html#avoidinstantiatingobjectsinloops&quot;&gt;AvoidInstantiatingObjectsInLoop&lt;/a&gt;,
which is at least loop-related. But there is no rule yet, that checks specifically for the mentioned
regex compilation calls.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Group With New Lines” suggests to use empty new lines in the code, to form code blocks, that belong
together semantically. I guess, that would be very difficult for a static analyzer to figure this out, as
it would need to really understand the code. It goes into the concept of cohesion. Theoretically, your
class would be so small, that it does only one thing, that there would be only one group and no need for
new lines. But that’s not the reality. In that sense, it might be easier to just detect big classes
like &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#godclass&quot;&gt;GodClass&lt;/a&gt; or just
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#excessivepubliccount&quot;&gt;ExcessivePublicCount&lt;/a&gt; or
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#toomanymethods&quot;&gt;TooManyMethods&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor Format Over Concatenation” reminds us, that java nowadays also supports C-like format strings.
If you are building a big string combining labels and values, it quickly becomes a mess and is very
difficult to read. A format string is most likely easier to read and should be used instead.
While PMD has a couple of rules that deal with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;StringBuilder&lt;/code&gt; and the concatenation strings in this
context, it doesn’t have such a rule. It shouldn’t be too difficult to detect InfixExpressions (“+”)
of type String (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;//InfixExpression[@Operator=&apos;+&apos;][pmd-java:typeIs(&quot;java.lang.String&quot;)]&lt;/code&gt;).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor Java API Over DIY” suggest to use the Java API and the ready to use methods. The book
brings two examples: Instead of doing a null check manually and throwing a NullPointerException,
just use &lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Objects.html#requireNonNull(T)&quot;&gt;Objects.requireNonNull&lt;/a&gt;
and instead of manually counting how often a given element is in a collection, use
&lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collections.html#frequency(java.util.Collection,java.lang.Object)&quot;&gt;Collections.frequency&lt;/a&gt;. PMD doesn’t have such a generic rule, that would find all the cases. And such a rule would
needed to be updated regularly and would also need to consider the used Java version, so that not a suggestion
of a method is made, that cannot be used, as it was only added in a later Java version.
There is however one rule, that goes into this: &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_performance.html#avoidarrayloops&quot;&gt;AvoidArrayLoops&lt;/a&gt;,
which suggests to use &lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/System.html#arraycopy(java.lang.Object,int,java.lang.Object,int,int)&quot;&gt;System.arraycopy&lt;/a&gt; or &lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Arrays.html#copyOf(T%5B%5D,int)&quot;&gt;Arrays.copyOf&lt;/a&gt;.
And the PMD rule &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_performance.html#usearraysaslist&quot;&gt;UseArraysAsList&lt;/a&gt;
suggest to use &lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Arrays.html#asList(T...)&quot;&gt;Arrays.asList&lt;/a&gt;
instead of a manual loop. So, there are already two PMD rules in this area, but for sure there
could be much more cases.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;commenting-code&quot;&gt;Commenting code&lt;/h3&gt;

&lt;p&gt;This chapter is all about comments. And while comments are welcomed in most cases, unhelpful or outdated comments
are bad.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;“Remove Superfluous Comments” addresses old comments (with wrong content) and comments, that don’t add
information, as they merely repeat the code (e.g. comments telling what is done instead of why is something done).
This reminds me of &lt;a href=&quot;https://jautodoc.sourceforge.net/&quot;&gt;JAutodoc&lt;/a&gt; which can generate JavaDoc comments without
any useful content. PMD has some rules in the category &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_documentation.html&quot;&gt;documentation&lt;/a&gt;
but these rules mostly do the opposite: They demand a comment. It would be difficult to write a rule, that
interprets the content and figures out whether it is superfluous (and can be removed) or not. But we could create
a rule, that identifies comments, that just repeats the method name and parameters and doesn’t add anything
additional (finding the generated JAutodoc comments that have never been changed). Another thing is “TODO” comments:
The recommendation in the book is - either fix the TODO immediately and remove the TODO comment, and if this is
not possible right away, create a new issue in the issue tracker and remove the TODO comment. We could create
a rule that find the TODO comments, like the checkstyle rule &lt;a href=&quot;https://checkstyle.org/checks/misc/todocomment.html#TodoComment&quot;&gt;TodoComment&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Remove Commented-Out Code” is about using your VCS to remember old implementations rather than using
comments. There is also no PMD rules for that. Commented out code has the problem, that it often doesn’t
work anymore when it is commented in, as the surrounding projects has evolved (e.g. refactored) and the
commented out code has been updated. In the end, it’s very likely, that the commented out code cannot be
used anyway. In regard to detecting this, there could be some regular expressions that detect
method calls. And the position of such a comment can also give a hint - e.g. inside an argument list.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Replace Comments with Constants” is a bit similar to “Replace Magic Numbers with Constants”. In the example
given in the book, the comments explained the meaning of the magic numbers. The comment could be used as
an additional information/suggestion when creation a violation message for the magic number, like
“Did you mean to create a constant named XYZ?”. In that sense, it would be an enhancement to the magic number
rule, but not a rule on its own.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Replace Comments with Utility Methods” gives an example, that sometimes a code block (some contiguous code lines)
are preceded by a single line comment. That seems to be an opportunity to refactor out this code block and
give it a name - exactly the name from the comment. I think, it would be quite difficult to reliably find
such comments, where such a refactoring would make sense, while ignoring the other comments.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Document Implementation Decisions” is about expanding short comments (like “fast implementation”) into
a more detailed comments, explaining the what, why, how of the decision that is made. There is no PMD
rule that can identify such comments and it would probably be quite difficult to implement such a rule.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Document Using Examples” makes the point, that the JavaDoc for a regex pattern is much more easier to understand
when examples are provided of some strings, that match, and some, that doesn’t. This however is again about
interpreting the content of a comment, which is out of scope for a static analyzer like PMD. Of course, we
could say, that a java.util.Pattern should be preceded with a comment, that has the two keywords
“Valid examples” and “Invalid examples”. That could identify exactly one special case (regex patterns), but
wouldn’t find other missing examples.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Structure JavaDoc of Packages” is about the content of the JavaDoc for packages. It should mention the most
important classes of the package and maybe give a usage example of those. All this is again out of scope
for PMD. There is the rule &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_documentation.html#commentrequired&quot;&gt;CommentRequired&lt;/a&gt;
which can detect missing comments, but doesn’t consider, whether the comment contains useful content.
And also, this rule is able to find missing JavaDocs on packages (e.g. in package-info.java).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Structure JavaDoc of Classes and Interfaces” is about ensuring a specific structure for the JavaDoc of types.
It should start with a summary sentence. While PMD itself can only ensure that there is a JavaDoc at all
(see &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_documentation.html#commentrequired&quot;&gt;CommentRequired&lt;/a&gt;),
it doesn’t check the contents. Checkstyle has a rule called &lt;a href=&quot;https://checkstyle.org/checks/javadoc/summaryjavadoc.html#SummaryJavadoc&quot;&gt;SummaryJavadoc&lt;/a&gt;
which does some checks on the first (summary) sentence. While a static analyzer can definitely verify
the there is a first sentence and that this is separated by a blank line from the rest of the doc, it can’t
verify that the content is useful at all.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Structure JavaDoc of Methods” and “Structure JavaDoc of Constructors” are similar like the one before: PMD
can only verify the existence of such Javadocs, but can’t check the contents. Checkstyle has some more
sophisticated checks for completeness, like all method parameters need to be documented
(see &lt;a href=&quot;https://checkstyle.org/checks/javadoc/javadocmethod.html#JavadocMethod&quot;&gt;JavadocMethod&lt;/a&gt;), but it
can’t verify the content.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;naming&quot;&gt;Naming&lt;/h3&gt;

&lt;p&gt;Naming is said to be one of the hardest things to get right in computer science. If you want to know the other,
see &lt;a href=&quot;https://martinfowler.com/bliki/TwoHardThings.html&quot;&gt;Two Hard Things&lt;/a&gt;. So, this chapter is all about naming
things in Java.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;“Use Java Naming Conventions” is about “correct” casing of types, fields, variables, methods and all that.
Luckily PMD has a rule already, actually several rules:
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#classnamingconventions&quot;&gt;ClassNamingConventions&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#fieldnamingconventions&quot;&gt;FieldNamingConventions&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#formalparameternamingconventions&quot;&gt;FormalParameterNamingConventions&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#genericsnaming&quot;&gt;GenericsNaming&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#localvariablenamingconventions&quot;&gt;LocalVariableNamingConventions&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#methodnamingconventions&quot;&gt;MethodNamingConventions&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#packagecase&quot;&gt;PackageCase&lt;/a&gt;.
Most of these rules can be configured, but that’s not the goal of the example the book makes: You should simply
use the &lt;a href=&quot;https://www.oracle.com/technetwork/java/codeconventions-150003.pdf&quot;&gt;Java Code Conventions&lt;/a&gt; as defined by Oracle,
and don’t invent your own conventions.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Follow Getter/Setter Conventions for Frameworks” says that if you create a JavaBean, you should follow the bean
conventions. The getter and setter methods and the field names must be connected. There are two PMD rules
for that purpose: &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#invalidjavabean&quot;&gt;InvalidJavaBean&lt;/a&gt;
and &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#booleangetmethodname&quot;&gt;BooleanGetMethodName&lt;/a&gt;.
Following such conventions spares you from surprises, that something doesn’t work as expected, when your
bean is used in a “standard” Java framework.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Single-Letter Names” is about naming of parameter names and local variables. Today, with the IDE’s help,
it not difficult anymore to avoid any too short names. The IDEs can most times help you with autocompleting the
variable names. So you often don’t have to type the full name. Longer names can provide so much more information
about the variables, that the code is much easier to understand. PMD has a couple of rules for that case as well:
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#shortclassname&quot;&gt;ShortClassName&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#shortmethodname&quot;&gt;ShortMethodName&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#shortvariable&quot;&gt;ShortVariable&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Abbreviations” might be picked up by the other short naming rules, since an abbreviation might
be short enough. But not always, e.g. “bufW” is a correct variable name and 4 letters long, but it
isn’t helpful. In the book’s example, it’s used as an abbreviation for “bufferedWriter” - which contains
too much detail - so “writer” is the sensible solution here. There is no PMD rule for that, but
a similar rule in Checkstyle: &lt;a href=&quot;https://checkstyle.org/checks/naming/abbreviationaswordinname.html&quot;&gt;AbbreviationAsWordInName&lt;/a&gt;.
In general, we could split the name into single words (e.g. a case change starts a new word) and verify
the length of the single words. The “bufW” would become “buf” and “W” and we have a short name.
Whether that’s an abbreviation can only be a guess.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Meaningless Terms” is a nice example about bad naming. In today’s software, you often find a bunch
of “Manager” classes or “Impl” suffixes. The most famous example referenced in the book is with the following
quote:&lt;/p&gt;

    &lt;blockquote&gt;
      &lt;p&gt;Roses are red,
leaves are green
but only Java has
AbstractSingletonProxyFactoryBean.&lt;/p&gt;
    &lt;/blockquote&gt;

    &lt;p&gt;It’s from &lt;a href=&quot;https://web.archive.org/web/20230601025839/http://bash.org/?962108&quot;&gt;http://bash.org/?962108 (wayback machine)&lt;/a&gt;
and it relates to the &lt;a href=&quot;https://github.com/spring-projects/spring-framework/blob/main/spring-aop/src/main/java/org/springframework/aop/framework/AbstractSingletonProxyFactoryBean.java&quot;&gt;AbstractSingletonProxyFactoryBean&lt;/a&gt;
in the spring framework.&lt;/p&gt;

    &lt;p&gt;There is no rule in PMD that finds such things. We could write a rule, that searches for some given meaningless
terms, but whether they are really meaningless can’t be decided. We could only highlight suspicious names.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Use Domain Terminology” is about using the correct terms when modeling the classes and methods. That way
the software is easier to understand (if you now the domain at least). Don’t invent new names for something, that
is already known. This is again very difficult for static analyzer, as it would require some level of
understanding, what the software really does.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;error-handling&quot;&gt;Error handling&lt;/h3&gt;

&lt;p&gt;This chapter is about proper error handling using exceptions and other means.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;“Fail Fast” is a simple code style, that avoids nested structures and keeps the logic flat. If you check
for an error condition at the start of a method and return or throw an exception directly, then you can be
sure, that for the remaining part of the method, this particular condition is already handled.
This makes the normal path of execution again more visible, as it is not hidden between other error condition
checks. There is no PMD rule for this, but it shouldn’t be too difficult to write a rule, that detects
methods that start with an “if” statement with multiple branches and each branch (except one) ends with a
throw statement.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Always Catch Most Specific Exception” is about the problem, when you simply catch “Exception”, which is
very unspecific: It could be any problem - and you handle all the different problems the same way. And
especially with unchecked exception, you might handle more cases than you wanted. But you want to have your
program crash when e.g. there is a NullPointerException, as this is a programming error.
There exists already the PMD rule &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#avoidcatchinggenericexception&quot;&gt;AvoidCatchingGenericException&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Explain Cause in Message” addresses the difficulty of good error messages. The simple text “An unexpected error
occurred” is not helpful - and this is already clear from the exception anyway. The book suggests a simple
pattern that one can follow: “Expected [EXPECTED], but got [ACTUAL] in [CONTEXT]”. This is much more
helpful in understand later on, what actually led to the error. For this, there is no direct PMD rule.
We could check however, that an exception isn’t created without a message at all. But the rule would be very
inaccurate. I guess, this means, that such a rule would be a bit more difficult to implement.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Breaking the Cause Chain” is about passing the original exception, when throwing a new exception.
This is easy: PMD has already a rule for that: &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#preservestacktrace&quot;&gt;PreserveStackTrace&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Expose Cause in Variable” reminds us, that exceptions are classes on its own and can contain more
structured information other than just the message. They can contain fields and methods as well.
This can be useful for other parts of the application, where one needs to handle the exceptional situation.
The suggestion is to create a custom exception subclass, that contains the interesting information pieces
as fields. There is a PMD rule &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#avoidthrowingrawexceptiontypes&quot;&gt;AvoidThrowingRawExceptionTypes&lt;/a&gt;,
but it doesn’t match this case exactly. I guess, it will be difficult to automatically detect, when a custom
exception would be useful. One heuristic could be: If the message is built as a string concatenation out of multiple
single components, then this would be an indication to just pass the components into the exception’s constructor
and have variables for these components.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Always Check Type Before Cast” suggests to always do a instanceof check before you do the cast. Not checking
the type before might result in a ClassCastException at runtime. There is no PMD rule for that, but it shouldn’t
be too difficult, to write a rule. The cast should be happening as the first statement in a if block, that checks
the type using “instanceof” - then it’s ok. A cast without such an if might be problematic.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Always Close Resources” suggest to simply use try-with-resources statement for dealing with resources. This
is available since Java 7 and ensures, that the resource is closed at the end. Luckily there is already
a PMD rule for that: &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#closeresource&quot;&gt;CloseResource&lt;/a&gt;.
Actually, there is another rule: &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#usetrywithresources&quot;&gt;UseTryWithResources&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Always Close Multiple Resources” is a variation of the case before and makes an example when you manually close
multiple resources in a finally block. You better just use a try-with-resources statement with declaring multiple
resources. The following PMD rule applies here: &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#usetrywithresources&quot;&gt;UseTryWithResources&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Explain Empty Catch” focuses on catch blocks, that contain no statements and just swallowing the exception.
It is unclear then, whether the exception handling has been forgotten to implement or whether this is intended
behavior in that case. PMD has the rule &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#emptycatchblock&quot;&gt;EmptyCatchBlock&lt;/a&gt;
for this.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;unit-testing&quot;&gt;Unit testing&lt;/h3&gt;

&lt;p&gt;This chapter gives some examples on what to pay attention to when writing unit tests.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;“Structure Tests Into Given-When-Then” gives advice how to make unit tests a bit more readable. Often you first
need to setup some test preconditions like the data. After that, you are executing the functionality under test.
And then you need to verify the final state and compare it your expectations. There is no PMD rule to check for
this example. And I think, it would be difficult to write one: PMD can’t understand, which lines of the unit test
belong to which semantic part (“given”, “when”, or “then”). We could check for the length of a unit test - if it
is too long (whatever this means), it is suspicious to be testing too much (e.g. several functionalities).
But that would be a different case.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Use Meaningful Assertions” is about using the correct assert-helper-methods when possible. E.g. instead of
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assertTrue(result == 1)&lt;/code&gt;, one should use the more appropriate method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assertEquals(1, result)&lt;/code&gt;. This method
also gives a better failure message, when the test fails: “Expected 1 but was xyz” instead of
“Expected true but was false”. PMD has a rule for that, that checks a couple of cases:
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#simplifiabletestassertion&quot;&gt;SimplifiableTestAssertion&lt;/a&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Expected Before Actual Value” is about calling &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assertEquals&lt;/code&gt; but using the wrong parameter order. For JUnit5
&lt;a href=&quot;https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/Assertions.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Assertions&lt;/code&gt;&lt;/a&gt;
and JUnit4 &lt;a href=&quot;https://junit.org/junit4/javadoc/latest/org/junit/Assert.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Assert&lt;/code&gt;&lt;/a&gt;,
the first parameter should be the expected value and the second parameter should be the actual value.
Mixing up the two parameters results in confusing test failure messages.
Note, that TestNG &lt;a href=&quot;https://javadoc.io/static/org.testng/testng/7.9.0/org/testng/Assert.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Assert&lt;/code&gt;&lt;/a&gt; uses a different
argument order. There is no PMD rule to detect this. To be 100% reliable, it would be very difficult to implement.
But for simple cases, where the expected value is a compile time constant, while the actual value is the result of
a method call or a variable, we could actually find such cases.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Use Reasonable Tolerance Values” suggests to use a delta when comparing floating point values. It’s easy
to forget this and there won’t be a compiler error, since the two double values would be boxed to Double and
then compared using equals. But exact comparisons of two double values aren’t reliable. JUnit provides an
overloaded &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assertEquals&lt;/code&gt; method for doubles that takes a third double argument as delta. There is no PMD
rule for this, but thanks to type resolution, it should be simple to implement a rule, that finds
assertEquals calls with doubles or floats, that only use 2 parameters.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Let JUnit Handle Exceptions” covers the old-style way of testing exception, before &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assertThrows()&lt;/code&gt; was
available or manually failing the test in a catch-block with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fail()&lt;/code&gt;. That has the downside, that you don’t see the
exception and therefore it might not be clear, why the test was failing. Interestingly, PMD doesn’t have a
rule for that, but it should be quite easy to implement.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Describe Your Tests” is about two similar things, and it is about naming. First, you test names should
have a meaningful name. With JUnit, you can be more descriptive by adding a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@DisplayName&lt;/code&gt; annotation. This
helps to understand, which functionality exactly is broken in case a unit test fails. However, PMD won’t
be able to assess whether the description is good enough and is exactly correct. It could only require
that every test is additionally annotated with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@DisplayName&lt;/code&gt; and that the test methods e.g. don’t start
with the prefix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt; - as this is not necessary anymore since JUnit4.&lt;br /&gt;
The other aspect of this example is, to give a clear reason, why a test is ignored. You can ignore tests with
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Disabled&lt;/code&gt; annotation - and this annotation allows you to specify some explanation, why the test has
been disabled. PMD could require, that such an explanation is always there for disabled tests, but it won’t
be able to asses, whether the explanation makes sense.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor Standalone Tests” is about a problem that arises, when setup methods like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@BeforeEach&lt;/code&gt; are used:
In order to understand a test, you also need to read the setup method in addition to the test itself. That means
you need to jump around in the test source. The tests are better comprehensible, if they have the complete
pattern “given-when-then” contained in each test. The given-part would otherwise be separated out into the
setup-method. So, the recommendation here is, to simply avoid &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@BeforeEach&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@BeforeAll&lt;/code&gt;. The setup-method
could still be shared, but just needs to be called explicitly. We could easily write a PMD rule to detect
any usages of these setup-methods.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Parametrize Your Tests” looks at a test pattern, where you test inside one method the same code, but with
different values. This means, you usually have already factored out the actual testcode, e.g. in a custom
assert method, which is called repeatedly with varying values. For this, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@ParameterizedTest&lt;/code&gt; is a good option:
It makes the test report more understandable, and you directly see, which of the different values is causing
a problem. There is no PMD rule for that, and I think it wouldn’t be so easy to identify a test, that
would be suitable to be refactored into a parameterized test. PMD could maybe detect the very simple cases,
but you immediately need to have some understanding of control flow, as there are some method calls
involved.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Cover the Edge Cases” is about test coverage and writing not only one test (“the happy case”) but also
considering failure cases and - as the title mentions - edge cases. As this all depends on the subject
under test, it seems to be very difficult to write a rule for that. We could write a rule, that detects
test classes, that only have one test case defined - but that’s probably a too simplistic heuristic.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;design-examples&quot;&gt;Design examples&lt;/h3&gt;

&lt;p&gt;This chapter is about design and how functions should not only be functional, but also beautiful and
convenient.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;“Split Method with Boolean Parameters” focuses on methods, that take a boolean parameter and contain a
big if-statement with two execution branches. This is a opportunity to make two methods out of this one
method, give a better name and avoid the ugly boolean parameter. If a PMD rule would exist for that,
it would be in the category “Design”. But there is no rule. Simple cases like the example in the book
can easily be detected, though.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Split Method with Optional Parameters” looks at methods, which take a parameter and that parameter is
part of a null check and early return. This might also be a candidate for a simple rule, that finds
such simple cases like in the book.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor Abstract Over Concrete Types” addresses the same problem, that the existing PMD rule
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#loosecoupling&quot;&gt;LooseCoupling&lt;/a&gt; does.
Interestingly, this rule is not in the “Design” category, but in “Best Practices”.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor Immutable Over Mutable State” is about designing value objects. If you objects are immutable, you
avoid lots of the problems in multi threading and consistency. Instead of mutating the object, a new
instance is returned instead, similar like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BigDecimal&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalDate&lt;/code&gt;. PMD has some related rules,
like &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#immutablefield&quot;&gt;ImmutableField&lt;/a&gt; or
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#uselessoperationonimmutable&quot;&gt;UselessOperationOnImmutable&lt;/a&gt;,
but no rule that suggests to write such immutable value classes in the first place. It might be
a bit difficult, to limit this rule and avoid too many false-positives. And it would be a rule, that
requires more refactoring and not a simple quick fix.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Combine State and Behavior” is about object oriented design and looks out for classes without methods
but only fields. Then there would be a second class, which contains only methods and take the first class
always as a parameter. First class represents the “State” and the second class represents the “Behavior”.
Another way to look at this is, that the methods could be static, as they don’t use any instance variables of
their own class. So, maybe that is an indication, that the method should be moved somewhere else.
Right now, there is no PMD rule for that. A first try could be rule, that finds methods, that could
be static.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Leaking References” is about encapsulating and protecting the inner state of a class.
PMD has a rule for arrays, that are leaking: &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#methodreturnsinternalarray&quot;&gt;MethodReturnsInternalArray&lt;/a&gt;.
But the book is right, this is not only about array, but also about collections.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Return Null”: PMD has a rule that suggests to return a empty collection, rather than null:
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#returnemptycollectionratherthannull&quot;&gt;ReturnEmptyCollectionRatherThanNull&lt;/a&gt;.
The book is more strict about this and suggests, that you should think about this, whenever you return
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; from a method. Avoiding null and returning a null object instead is a common pattern, that can avoid
null pointer exception later.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;using-streams&quot;&gt;Using streams&lt;/h3&gt;

&lt;p&gt;Streams have been introduced in Java 8 with &lt;a href=&quot;https://openjdk.org/jeps/107&quot;&gt;JEP 107&lt;/a&gt;. These provide a functional way to
express, what should happen in data processing instead of how. Using streams often means, that lambdas and method
references are used. And there are a couple of things to consider in terms of readability and maintainability.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor Lambdas Over Anonymous Classes” shows an example with a simple anonymous class, that implements a
a functional interface. This can be written more concise without the boilerplate as a lambda expression. The
specific example here is by the way not directly related to streams - it uses the method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;computeIfAbsent&lt;/code&gt;, which
is part of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Map&lt;/code&gt; and takes a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Function&amp;lt;T, R&amp;gt;&lt;/code&gt; as an argument. There is no PMD rule for that. But it
should be simple thanks to type resolution, to figure out whether the implemented anonymous class is actually
implementing a single abstract method (or a functional interface) and suggest to use a lambda instead.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor Functional Over Imperative Style” is now really an example, that suggest to use streams. The example
uses a for-each loop with an if-condition inside and collecting the result in a list after performing a mapping
of the values. There is no PMD rule for this. Creating such a rule might result in lots of false positives,
so that rule could only say: “This for-each loop probably can be converted into a stream pipeline, but I’m
not 100% sure”. I guess, this rule would be a bit more difficult to implement reliably.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor Method References Over Lambdas” is easy: There is a PMD rule already existing:
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#lambdacanbemethodreference&quot;&gt;LambdaCanBeMethodReference&lt;/a&gt;.
Using a method reference can express the intention more concise and is therefore simpler to read and understand.
The used methods themselves can be tested independently of their usage in the stream pipeline.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Side Effects” provides an example of a stream pipeline, that uses as the last stage the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forEach&lt;/code&gt;
method. In this last stage, a object defined outside of the stream is modified - the side effect. And this
is not pure functional programming style anymore. There is no PMD rule for that and it seems, that a reliable
rule is not so easy to implement. We could start with verifying the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forEach&lt;/code&gt; call - it can be used as a terminating
(last) operation. But if it modifies some other object, this might cause problems, if the stream is executed
in parallel, as the terminating operation might not be executed in the original order or even in multiple threads.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Use Collect for Terminating Complex Streams” suggests to use instead of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;forEach&lt;/code&gt; operation, which then collects
each element of the stream manually into a collection, to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;collect&lt;/code&gt; instead with the various provided
collectors, such as grouping. In the given example, the end result of the method is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Map&lt;/code&gt; of some kind, but
the stream doesn’t create this map with collect, but rather using forEach with side effects. The Map is a local
variable outside of the stream. Similar to the last example, there is no PMD rule for this. And it seems to so
simple to implement a good rule for that. We could only point to suspicious stream pipelines.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Exceptions in Streams” gives an example on how to deal with (checked) exceptions that need to be handled
in the lambda expressions used for the stream operations. In functional style programming, functions should
produce an output given a specific input - exceptions don’t fit into that scheme. Throwing a RuntimeException
inside a stream pipeline make the whole stream not producing a result anymore and just end with an exception.
To avoid this, in this example it is suggested to ignore the element of the stream, that caused the exception
and let the stream continue to compute the result. This can be achieved by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flatMap&lt;/code&gt; and returning a
single element Stream via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream.of()&lt;/code&gt; in the successful case and an empty stream (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream.empty()&lt;/code&gt;) in the
exceptional case - effectively skipping the element if there is a problem with that. This of course depends
heavily on the actual use case, whether you can ignore an exception or not. There is no PMD rule for that,
but one could be written that detects throwing of exceptions inside a lambda expression, that is used in
a stream pipeline.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor Optional Over Null” is about communicating explicitly, if a return value of a method could return
nothing (aka. an empty optional or null). Just returning &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; would require to do a null check by the caller
of the method, but that could be easily forgotten and resulting in a NullPointerException. Returning an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Optional&amp;lt;T&amp;gt;&lt;/code&gt;
instead forces the caller to think about, what to do, if there is no result from the method call, and thus can
avoid NullPointerExceptions. There doesn’t seem to be a PMD rule for detecting this. We could start with simple
cases, where a method explicitly returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; in some execution path. But this wouldn’t detect all potential cases.
The rule would error on the false negative side, which might be ok for the beginning. Another angle for detection
could be to look at method calls. If there is directly after the method call a null-check, then the code
assumes that the method could return null, so we could report a violation here.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Avoid Optional Fields or Parameters” is about a different usage of Optional. The last example suggested to use
Optional as a return type of methods. When returning an Optional, you &lt;em&gt;never&lt;/em&gt; return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; for that method.
But if you have a method parameter with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Optional&lt;/code&gt;, a caller could provide &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; - and now you need to deal with
three possible values: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt;, empty &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Optional&lt;/code&gt;, and a value wrapped in Optional. This just makes implementing
the method just more complicated. It would be simpler, if the method just allowed to be called with a non-null
value and throws an exception, if it is called with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;null&lt;/code&gt; (e.g. via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Objects.requireNonNull&lt;/code&gt;). The same
applies for fields, as they can then have potentially the same three possible values and every access to the field
in the class is more complicated. There is no PMD rule for this, but the rule is straightforward. We just need
to look for usages of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Optional&lt;/code&gt; in field declarations and in the argument list of method declarations.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Use Optionals as Streams” suggests a more functional approach when using Optionals: Instead of checking with
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isPresent()&lt;/code&gt; and then getting the value explicitly, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Optional&lt;/code&gt; class has a lot of convenience functions
that are very similar to the stream functions: like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;filter&lt;/code&gt;. Additionally, it as terminating functions
like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;orElseThrow&lt;/code&gt;. There is no PMD rule for this. A potential rule could look for a “isPresent” method call
followed by “get” method call to find suspicious usages of Optional. The rule might find usages, that maybe are
not so easily adaptable to the suggested functional style.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;production-readiness&quot;&gt;Production readiness&lt;/h3&gt;

&lt;p&gt;This chapter is partially about general advice, such as “Use Static Code Analysis Tools”. Even &lt;a href=&quot;https://pmd.github.io&quot;&gt;PMD&lt;/a&gt;
is suggested, among other tools like &lt;a href=&quot;https://spotbugs.github.io&quot;&gt;SpotBugs&lt;/a&gt;,
&lt;a href=&quot;http://checkstyle.sourceforge.net&quot;&gt;Checkstyle&lt;/a&gt;, &lt;a href=&quot;http://errorprone.info&quot;&gt;Error Prone&lt;/a&gt;,
or IntelliJ IDEAs &lt;a href=&quot;https://www.jetbrains.com/help/idea/code-inspection.html&quot;&gt;Code Inspections&lt;/a&gt;.
Other general advice is: Use a common code format, use automated builds and continuous integration.
But there also some more concrete examples.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;“Favor Logging Over Console Output” targets the usage of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.out&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.err&lt;/code&gt;. Instead of these,
you should use a proper logging framework. Such a framework can easily be configured with a log level to
output debug messages only in certain situations and they also provide a way to log exception stacktraces
properly. PMD has a couple of related rules for using logging, such as
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#guardlogstatement&quot;&gt;GuardLogStatement&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#invalidlogmessageformat&quot;&gt;InvalidLogMessageFormat&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#morethanonelogger&quot;&gt;MoreThanOneLogger&lt;/a&gt;,
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#properlogger&quot;&gt;ProperLogger&lt;/a&gt;,
and &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#usecorrectexceptionlogging&quot;&gt;UseCorrectExceptionLogging&lt;/a&gt;.
All these rules already assume, that a logging framework is used. There is two additional rules:
&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#avoidprintstacktrace&quot;&gt;AvoidPrintStackTrace&lt;/a&gt;
finds calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exception.printStackTrace()&lt;/code&gt; and it says “use a logger call instead”.
And there is the rule &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#systemprintln&quot;&gt;SystemPrintln&lt;/a&gt;
which flags any usages of System.out/err. Exactly what this is about. It simply suggests to use a logger.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Speed Up Your Program” is a simple tip when using Streams: Streams might be sped up when using parallel streams.
This however is only possible, if the single stream operations are indeed free of side effects and depending on the
stream (if it is sorted), a parallel stream might not always be faster. We don’t have a rule in PMD for that.
Creating a rule, that identifies streams, that are not yet parallelized, is simple. But the rule can only give the
suggestion, to try out, whether using a parallel stream would make your program faster - as it depends not only
on the stream operations but also on the amount of data, that is being processed.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;“Know Your Falsehoods” is the last concrete example in the book. It is about making too simplistic assumptions
about the real world. The example show some code, that parses a given name of a person and extracts the surname.
However, the surname might not always be provided as the last word - and it could be composed of actually two
words separated by a space. Not to mention other regions (i18n, l10n). The solution for this concrete problem
is, to make no assumption at all and just ask for the surname directly. This is definitely an example, that
can’t be formalized as a PMD rule.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;The book contains a lot of examples. And if you like to learn from examples (bad and good), that’s are very
useful book. My goal here was to map the examples and suggestions to existing PMD rules and to rules, that could
be potentially implemented. That way, you have an easy reminder, if you don’t follow the suggested practices.&lt;/p&gt;

&lt;p&gt;Even if you are not new anymore to developing software, it’s still a good book to remind you of the good
practices, that are maybe forgotten in the hectic everyday life.&lt;/p&gt;

&lt;p&gt;Im closing with a couple of tables, that contain the mapping between the examples from the book and the PMD
rules. It would be great to update the rule documentations, to mention the book and improve the reasoning
behind the rules.&lt;/p&gt;

&lt;h3 id=&quot;existing-pmd-rules&quot;&gt;Existing PMD rules&lt;/h3&gt;

&lt;table class=&quot;table table-bordered table-striped&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Example Title&lt;/th&gt;
      &lt;th&gt;PMD Rule&lt;/th&gt;
      &lt;th&gt;Category in PMD&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Unnecessary Comparisons&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#simplifybooleanexpressions&quot;&gt;SimplifyBooleanExpressions&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Design&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Return Boolean Expressions Directly&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#simplifybooleanreturns&quot;&gt;SimplifyBooleanReturns&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Design&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Switch Fallthrough&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#implicitswitchfallthrough&quot;&gt;ImplicitSwitchFallThrough&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Error Prone&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Always Use Braces&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#controlstatementbraces&quot;&gt;ControlStatementBraces&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Code Style&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor For-Each Over For Loops&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#forloopcanbeforeach&quot;&gt;ForLoopCanBeForeach&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Best Practices&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Use Java Naming Conventions&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#classnamingconventions&quot;&gt;ClassNamingConventions&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#fieldnamingconventions&quot;&gt;FieldNamingConventions&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#formalparameternamingconventions&quot;&gt;FormalParameterNamingConventions&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#genericsnaming&quot;&gt;GenericsNaming&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#localvariablenamingconventions&quot;&gt;LocalVariableNamingConventions&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#methodnamingconventions&quot;&gt;MethodNamingConventions&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#packagecase&quot;&gt;PackageCase&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Code Style&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Follow Getter/Setter Conventions for Frameworks&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#invalidjavabean&quot;&gt;InvalidJavaBean&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#booleangetmethodname&quot;&gt;BooleanGetMethodName&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Design and Code Style&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Single-Letter Names&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#shortclassname&quot;&gt;ShortClassName&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#shortmethodname&quot;&gt;ShortMethodName&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#shortvariable&quot;&gt;ShortVariable&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Code Style&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Always Catch Most Specific Exception&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#avoidcatchinggenericexception&quot;&gt;AvoidCatchingGenericException&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Design&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Breaking the Cause Chain&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#preservestacktrace&quot;&gt;PreserveStackTrace&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Best Practices&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Always Close Resources&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#closeresource&quot;&gt;CloseResource&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#usetrywithresources&quot;&gt;UseTryWithResources&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Error Prone and Best Practices&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Always Close Multiple Resources&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#usetrywithresources&quot;&gt;UseTryWithResources&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Best Practices&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Explain Empty Catch&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#emptycatchblock&quot;&gt;EmptyCatchBlock&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Error Prone&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Use Meaningful Assertions&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#simplifiabletestassertion&quot;&gt;SimplifiableTestAssertion&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Best Practices&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor Abstract Over Concrete Types&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#loosecoupling&quot;&gt;LooseCoupling&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Best Practices&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor Method References Over Lambdas&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#lambdacanbemethodreference&quot;&gt;LambdaCanBeMethodReference&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Code Style&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor Logging Over Console Output&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#avoidprintstacktrace&quot;&gt;AvoidPrintStackTrace&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#systemprintln&quot;&gt;SystemPrintln&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;Best Practices&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;potential-pmd-rules&quot;&gt;Potential PMD rules&lt;/h3&gt;

&lt;p&gt;These are the examples, for which no direct counterpart in PMD exists. But on first glance, it sounds feasibly to implement PMD rules in the future.&lt;/p&gt;

&lt;table class=&quot;table table-bordered table-striped&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Example Title&lt;/th&gt;
      &lt;th&gt;Related existing rules&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Negations&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_codestyle.html#confusingternary&quot;&gt;ConfusingTernary&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Simplify Boolean Expressions&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#cyclomaticcomplexity&quot;&gt;CyclomaticComplexity&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid NullPointerException in Conditionals&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#literalsfirstincomparisons&quot;&gt;LiteralsFirstInComparisons&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#brokennullcheck&quot;&gt;BrokenNullCheck&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#equalsnull&quot;&gt;EqualsNull&lt;/a&gt; , &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#misplacednullcheck&quot;&gt;MisplacedNullCheck&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#unusednullcheckinequals&quot;&gt;UnusedNullCheckInEquals&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Replace Magic Numbers with Constants&lt;/td&gt;
      &lt;td&gt;Checkstyle &lt;a href=&quot;https://checkstyle.org/checks/coding/magicnumber.html#MagicNumber&quot;&gt;MagicNumber&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Collection Modification During Iteration&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Compute-Intense Operations During Iteration&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_apex_performance.html#operationwithhighcostinloop&quot;&gt;OperationWithHighCostInLoop&lt;/a&gt; (Apex), &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_performance.html#avoidinstantiatingobjectsinloops&quot;&gt;AvoidInstantiatingObjectsInLoop&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor Format Over Concatenation&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor Java API Over DIY&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_performance.html#avoidarrayloops&quot;&gt;AvoidArrayLoops&lt;/a&gt;, &lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_performance.html#usearraysaslist&quot;&gt;UseArraysAsList&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Remove Superfluous Comments&lt;/td&gt;
      &lt;td&gt;Checkstyle &lt;a href=&quot;https://checkstyle.org/checks/misc/todocomment.html#TodoComment&quot;&gt;TodoComment&lt;/a&gt;, &lt;a href=&quot;https://jautodoc.sourceforge.net/&quot;&gt;JAutodoc&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Remove Commented-Out Code&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Abbreviations&lt;/td&gt;
      &lt;td&gt;Checkstyle &lt;a href=&quot;https://checkstyle.org/checks/naming/abbreviationaswordinname.html&quot;&gt;AbbreviationAsWordInName&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Fail Fast&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Expose Cause in Variable&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_design.html#avoidthrowingrawexceptiontypes&quot;&gt;AvoidThrowingRawExceptionTypes&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Always Check Type Before Cast&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Expected Before Actual Value&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Use Reasonable Tolerance Values&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Let JUnit Handle Exceptions&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor Standalone Tests&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Split Method with Boolean Parameters&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Split Method with Optional Parameters&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Leaking References&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_bestpractices.html#methodreturnsinternalarray&quot;&gt;MethodReturnsInternalArray&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Return Null&lt;/td&gt;
      &lt;td&gt;&lt;a href=&quot;https://docs.pmd-code.org/latest/pmd_rules_java_errorprone.html#returnemptycollectionratherthannull&quot;&gt;ReturnEmptyCollectionRatherThanNull&lt;/a&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor Lambdas Over Anonymous Classes&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Exceptions in Streams&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor Optional Over Null&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Optional Fields or Parameters&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Use Optionals as Streams&lt;/td&gt;
      &lt;td&gt;n/a&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;difficult-rules&quot;&gt;Difficult rules&lt;/h3&gt;

&lt;p&gt;These are the examples, for which is sounds very difficult to implement a reliable PMD rule for detection.&lt;/p&gt;

&lt;table class=&quot;table table-bordered table-striped&quot;&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Example Title&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Ensure Code Symmetry&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor Enums Over Integer Constants&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Group With New Lines&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Replace Comments with Constants&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Replace Comments with Utility Methods&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Document Implementation Decisions&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Document Using Examples&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Structure JavaDoc of Packages&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Structure JavaDoc of Classes and Interfaces&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Structure JavaDoc of Methods&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Structure JavaDoc of Constructors&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Meaningless Terms&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Use Domain Terminology&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Explain Cause in Message&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Structure Tests Into Given-When-Then&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Describe Your Tests&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Parametrize Your Tests&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cover the Edge Cases&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor Immutable Over Mutable State&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Combine State and Behavior&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Favor Functional Over Imperative Style&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Avoid Side Effects&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Use Collect for Terminating Complex Streams&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Speed Up Your Program&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Know Your Falsehoods&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

</description>
        <pubDate>Sun, 23 Feb 2025 00:00:00 +0100</pubDate>
        <link>https://adangel.org/2025/02/23/java-by-comparison-and-PMD/</link>
        <guid isPermaLink="true">https://adangel.org/2025/02/23/java-by-comparison-and-PMD/</guid>
        
        <category>pmd</category>
        
        
      </item>
    
      <item>
        <title>Repairing broken EDID of monitor</title>
        <description>&lt;h2 id=&quot;introduction&quot;&gt;Introduction&lt;/h2&gt;

&lt;p&gt;Recently I tried out a HDMI stick at my monitor. It’s the same monitor, I repaired back
then in &lt;a href=&quot;/2023/12/17/repairing-hyundai-W2420D/&quot;&gt;Repairing Hyundai W240D monitor&lt;/a&gt;. After I was finished
with the HDMI stick I reconnected my laptop again, but - uh oh: The resolution was not recognized anymore.&lt;/p&gt;

&lt;p&gt;I was presented with an ugly 640x480 resolution. The first things like plugging the monitor in again or switching
it off and on again didn’t help. I could select between 640x480, 800x600 and 1024x768. And the monitor was
recognized as “unnamed monitor”.&lt;/p&gt;

&lt;p&gt;So, looking at the logs - maybe I see some error. And I got lucky: The kernel logged the following line:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kernel: EDID block 0 (tag 0x00) checksum is invalid, remainder is 66
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s strange. Seems like the monitor doesn’t identify itself anymore. Searching the interwebs brought me
to a wonderful page, which exactly explains, what I need to do: &lt;a href=&quot;https://wiki.debian.org/RepairEDID&quot;&gt;https://wiki.debian.org/RepairEDID&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The following is a step-by-step description of what I did to get it working again. In case, it happens again,
I hopefully remember that this issue can be fixed.&lt;/p&gt;

&lt;h2 id=&quot;the-commands&quot;&gt;The commands&lt;/h2&gt;

&lt;h3 id=&quot;reading-the-edid-eeprom&quot;&gt;Reading the EDID EEPROM&lt;/h3&gt;

&lt;p&gt;First step is to read out the EDID EEPROM. We use two kernel modules to get access to this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ modprobe i2c-dev
$ modprobe eeprom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we can search for EDID EEPROMs. These have the property, that they are at address &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x0050&lt;/code&gt; on the i2c bus:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ls -al /sys/bus/i2c/devices/*-0050/eeprom
-r--r--r-- 1 root root 256 26. Nov 12:09 /sys/bus/i2c/devices/6-0050/eeprom
-r--r--r-- 1 root root 256 26. Nov 12:09 /sys/bus/i2c/devices/7-0050/eeprom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These are the two EEPROMs I have. Both are 256 bytes big.
One is for the internal laptop display, the other is the broken monitor.
We can verify the contents of this EEPROMs by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;parse-edid&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ apt install read-edid
$ cat /sys/bus/i2c/devices/6-0050/eeprom|parse-edid
Checksum Correct

Section &quot;Monitor&quot;
	Identifier &quot;&quot;
	ModelName &quot;&quot;
	VendorName &quot;SHP&quot;
	# Monitor Manufactured week 50 of 2014
	# EDID version 1.4
	# Digital Display
	DisplaySize 290 170
	Gamma 2.20
	Option &quot;DPMS&quot; &quot;false&quot;
	Modeline 	&quot;Mode 0&quot; -hsync -vsync 
EndSection
$ cat /sys/bus/i2c/devices/7-0050/eeprom|parse-edid
WARNING: Checksum failed
Trying to continue...
Section &quot;Monitor&quot;
	Identifier &quot;W240D DVI&quot;
	ModelName &quot;W240D DVI&quot;
	VendorName &quot;HIT&quot;
	# Monitor Manufactured week 30 of 2007
	# EDID version 1.3
	# Digital Display
	DisplaySize 520 320
	Gamma 2.20
	Option &quot;DPMS&quot; &quot;false&quot;
	Horizsync 30-83
	VertRefresh 59-76
	# Maximum pixel clock is 180MHz
	#Not giving standard mode: 1920x1200, 60Hz
	#Not giving standard mode: 1600x1200, 60Hz
	#Not giving standard mode: 1680x1050, 60Hz
	#Not giving standard mode: 1280x1024, 60Hz

	#Extension block found. Parsing...
	Modeline 	&quot;Mode 11&quot; -hsync -vsync 
	Modeline 	&quot;Mode 0&quot; +hsync -vsync 
	Modeline 	&quot;Mode 1&quot; +hsync +vsync 
	Modeline 	&quot;Mode 2&quot; 148.500 1920 2008 2052 2200 1080 1084 1089 1125 +hsync +vsync
	Modeline 	&quot;Mode 3&quot; 74.250 1920 2448 2492 2640 1080 1082 1089 1125 +hsync +vsync interlace
	Modeline 	&quot;Mode 4&quot; 74.250 1280 1720 1760 1980 720 725 730 750 +hsync +vsync
	Modeline 	&quot;Mode 5&quot; 27.000 720 732 796 864 576 581 586 625 -hsync -vsync
	Modeline 	&quot;Mode 6&quot; 74.250 1920 2008 2052 2200 1080 1082 1087 1125 +hsync +vsync interlace
	Modeline 	&quot;Mode 7&quot; 74.250 1280 1390 1420 1650 720 725 730 750 +hsync +vsync
	Modeline 	&quot;Mode 8&quot; 27.027 720 736 798 858 480 489 495 525 -hsync -vsync
	Modeline 	&quot;Mode 9&quot; 27.027 720 736 798 858 480 489 495 525 -hsync -vsync
	Modeline 	&quot;Mode 10&quot; 25.200 640 656 752 800 480 490 492 525 -hsync -vsync
	Modeline 	&quot;Mode 12&quot; +hsync +vsync interlace
	Modeline 	&quot;Mode 13&quot; +hsync +vsync 
	Modeline 	&quot;Mode 14&quot; -hsync -vsync 
	Option &quot;PreferredMode&quot; &quot;Mode 11&quot;
EndSection
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The second one is the problematic one, as indicated by “WARNING: Checksum failed”. But most data seems still
to be there, so not too bad. The EEPROM is still there and didn’t loose all the data.&lt;/p&gt;

&lt;h3 id=&quot;a-hexdump-and-the-difference&quot;&gt;A hexdump and the difference&lt;/h3&gt;

&lt;p&gt;Let’s have a look at the hexdump:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cat /sys/bus/i2c/devices/7-0050/eeprom|hd
00000000  00 ff ff ff ff ff ff 00  21 34 03 7d 43 41 32 01  |........!4.}CA2.|
00000010  1e 11 01 03 80 34 20 78  0a ef 95 a3 54 4c 9b 26  |.....4 x....TL.&amp;amp;|
00000020  00 50 54 ad cf 00 d1 00  a9 40 b3 00 81 80 01 01  |.PT......@......|
00000030  01 01 01 01 01 01 28 3c  80 a0 70 b0 23 40 30 20  |......(&amp;lt;..p.#@0 |
00000040  36 00 08 40 21 00 00 1a  48 3f 40 30 62 b0 32 40  |6..@!...H?@0b.2@|
00000050  40 c0 13 00 08 40 21 00  00 1e 00 00 00 fd 00 3b  |@....@!........;|
00000060  4c 1e 53 12 00 0a 20 20  20 20 20 20 00 00 00 fc  |L.S...      ....|
00000070  00 57 32 34 30 44 20 44  56 49 0a 20 20 20 01 33  |.W240D DVI.   .3|
00000080  02 03 1c 71 49 90 14 13  12 05 04 03 02 01 23 09  |...qI.........#.|
00000090  07 07 83 01 00 00 65 03  0c 00 10 00 8c 0a d0 90  |......e.........|
000000a0  20 40 31 20 0c 40 55 00  13 8e 21 00 00 18 01 1d  | @1 .@U...!.....|
000000b0  80 18 71 1c 16 20 58 2c  25 00 c4 8e 21 00 00 9e  |..q.. X,%...!...|
000000c0  01 1d 00 72 51 d0 1e 20  6e 28 55 00 c4 8e 21 00  |...rQ.. n(U...!.|
000000d0  00 1e 8c 0a d0 8a 20 e0  2d 10 10 3e 96 00 c4 8e  |...... .-..&amp;gt;....|
000000e0  21 00 00 18 8c 0a d0 8a  20 e0 2d 10 10 3e 96 00  |!....... .-..&amp;gt;..|
000000f0  13 8e 21 00 00 18 00 00  00 00 00 00 00 00 00 c4  |..!.............|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;These are the 256 bytes of the EEPROM for which the checksum is not correct.&lt;/p&gt;

&lt;p&gt;Luckily, I also backed up the EDID data when I repaired the monitor the last time. This backup is
in the form of a blog post, see &lt;a href=&quot;/2023/12/17/repairing-hyundai-W2420D/&quot;&gt;Repairing Hyundai W240D monitor&lt;/a&gt;.
Now, taking the correct EEPROM content at that time:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cat /sys/class/drm/card0/card0-DP-1/edid | hd&lt;/code&gt;:&lt;/p&gt;

  &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;00000000  00 ff ff ff ff ff ff 00  21 34 03 7d 43 41 32 01  |........!4.}CA2.|
00000010  1e 11 01 03 80 34 20 78  0a ef 95 a3 54 4c 9b 26  |.....4 x....TL.&amp;amp;|
00000020  0f 50 54 ad cf 00 d1 00  a9 40 b3 00 81 80 01 01  |.PT......@......|
00000030  01 01 01 01 01 01 28 3c  80 a0 70 b0 23 40 30 20  |......(&amp;lt;..p.#@0 |
00000040  36 00 08 40 21 00 00 1a  48 3f 40 30 62 b0 32 40  |6..@!...H?@0b.2@|
00000050  40 c0 13 00 08 40 21 00  00 1e 00 00 00 fd 00 3b  |@....@!........;|
00000060  4c 1e 53 12 00 0a 20 20  20 20 20 20 00 00 00 fc  |L.S...      ....|
00000070  00 57 32 34 30 44 20 44  56 49 0a 20 20 20 01 33  |.W240D DVI.   .3|
00000080  02 03 1c 71 49 90 14 13  12 05 04 03 02 01 23 09  |...qI.........#.|
00000090  07 07 83 01 00 00 65 03  0c 00 10 00 8c 0a d0 90  |......e.........|
000000a0  20 40 31 20 0c 40 55 00  13 8e 21 00 00 18 01 1d  | @1 .@U...!.....|
000000b0  80 18 71 1c 16 20 58 2c  25 00 c4 8e 21 00 00 9e  |..q.. X,%...!...|
000000c0  01 1d 00 72 51 d0 1e 20  6e 28 55 00 c4 8e 21 00  |...rQ.. n(U...!.|
000000d0  00 1e 8c 0a d0 8a 20 e0  2d 10 10 3e 96 00 c4 8e  |...... .-..&amp;gt;....|
000000e0  21 00 00 18 8c 0a d0 8a  20 e0 2d 10 10 3e 96 00  |!....... .-..&amp;gt;..|
000000f0  13 8e 21 00 00 18 00 00  00 00 00 00 00 00 00 c4  |..!.............|
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;Can you spot the difference? It’s actually only one single byte, that’s bad: it’s at offset &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x20&lt;/code&gt;: The bad
one has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt; but it should be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x0f&lt;/code&gt;. No idea, what cause this change…&lt;/p&gt;

&lt;h3 id=&quot;writing-to-the-eeprom&quot;&gt;Writing to the EEPROM&lt;/h3&gt;

&lt;p&gt;First step: Let’s backup the current state of the EEPROM in case we mess up, it will be handy:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cat /sys/bus/i2c/devices/7-0050/eeprom &amp;gt; hyundai-monitor-bad-edid.bin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next step: We will use the i2c utils to write to it. So, let’s first use this tool to dump the content, so that
we are sure, we have the correct EEPROM… In the docs I read, there are many warnings about writing to the wrong
EEPROM - e.g. the DIMMs also have a EEPROM which store the timing data. If you accidentally write to the wrong
EEPROM and write to the DIMMs of your laptop, you practically have bricked your laptop. So, better to be sure.&lt;/p&gt;

&lt;p&gt;It should be i2c bus number 7 and address 0x50:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ i2cdump 7 0x50
No size specified (using byte-data access)
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-7, address 0x50, mode byte
Continue? [Y/n] y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 00 ff ff ff ff ff ff 00 21 34 03 7d 43 41 32 01    ........!4?}CA2?
10: 1e 11 01 03 80 34 20 78 0a ef 95 a3 54 4c 9b 26    ?????4 x????TL?&amp;amp;
20: 00 50 54 ad cf 00 d1 00 a9 40 b3 00 81 80 01 01    .PT??.?.?@?.????
30: 01 01 01 01 01 01 28 3c 80 a0 70 b0 23 40 30 20    ??????(&amp;lt;??p?#@0 
40: 36 00 08 40 21 00 00 1a 48 3f 40 30 62 b0 32 40    6.?@!..?H?@0b?2@
50: 40 c0 13 00 08 40 21 00 00 1e 00 00 00 fd 00 3b    @??.?@!..?...?.;
60: 4c 1e 53 12 00 0a 20 20 20 20 20 20 00 00 00 fc    L?S?.?      ...?
70: 00 57 32 34 30 44 20 44 56 49 0a 20 20 20 01 33    .W240D DVI?   ?3
80: 02 03 1c 71 49 90 14 13 12 05 04 03 02 01 23 09    ???qI?????????#?
90: 07 07 83 01 00 00 65 03 0c 00 10 00 8c 0a d0 90    ????..e??.?.????
a0: 20 40 31 20 0c 40 55 00 13 8e 21 00 00 18 01 1d     @1 ?@U.??!..???
b0: 80 18 71 1c 16 20 58 2c 25 00 c4 8e 21 00 00 9e    ??q?? X,%.??!..?
c0: 01 1d 00 72 51 d0 1e 20 6e 28 55 00 c4 8e 21 00    ??.rQ?? n(U.??!.
d0: 00 1e 8c 0a d0 8a 20 e0 2d 10 10 3e 96 00 c4 8e    .????? ?-??&amp;gt;?.??
e0: 21 00 00 18 8c 0a d0 8a 20 e0 2d 10 10 3e 96 00    !..????? ?-??&amp;gt;?.
f0: 13 8e 21 00 00 18 00 00 00 00 00 00 00 00 00 c4    ??!..?.........?
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, we see the same data. And at offset &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x20&lt;/code&gt; there is still the wrong byte &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x00&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now let’s fix it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ i2cset -y 7 0x50 0x20 0x0F
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This sets the byte at offset 0x20 to 0x0F for the EEPROM on bus 7 and address 0x50.&lt;/p&gt;

&lt;p&gt;Dump it again, to verify it worked:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ i2cdump 7 0x50
No size specified (using byte-data access)
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-7, address 0x50, mode byte
Continue? [Y/n] y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 00 ff ff ff ff ff ff 00 21 34 03 7d 43 41 32 01    ........!4?}CA2?
10: 1e 11 01 03 80 34 20 78 0a ef 95 a3 54 4c 9b 26    ?????4 x????TL?&amp;amp;
20: 0f 50 54 ad cf 00 d1 00 a9 40 b3 00 81 80 01 01    ?PT??.?.?@?.????
30: 01 01 01 01 01 01 28 3c 80 a0 70 b0 23 40 30 20    ??????(&amp;lt;??p?#@0 
40: 36 00 08 40 21 00 00 1a 48 3f 40 30 62 b0 32 40    6.?@!..?H?@0b?2@
50: 40 c0 13 00 08 40 21 00 00 1e 00 00 00 fd 00 3b    @??.?@!..?...?.;
60: 4c 1e 53 12 00 0a 20 20 20 20 20 20 00 00 00 fc    L?S?.?      ...?
70: 00 57 32 34 30 44 20 44 56 49 0a 20 20 20 01 33    .W240D DVI?   ?3
80: 02 03 1c 71 49 90 14 13 12 05 04 03 02 01 23 09    ???qI?????????#?
90: 07 07 83 01 00 00 65 03 0c 00 10 00 8c 0a d0 90    ????..e??.?.????
a0: 20 40 31 20 0c 40 55 00 13 8e 21 00 00 18 01 1d     @1 ?@U.??!..???
b0: 80 18 71 1c 16 20 58 2c 25 00 c4 8e 21 00 00 9e    ??q?? X,%.??!..?
c0: 01 1d 00 72 51 d0 1e 20 6e 28 55 00 c4 8e 21 00    ??.rQ?? n(U.??!.
d0: 00 1e 8c 0a d0 8a 20 e0 2d 10 10 3e 96 00 c4 8e    .????? ?-??&amp;gt;?.??
e0: 21 00 00 18 8c 0a d0 8a 20 e0 2d 10 10 3e 96 00    !..????? ?-??&amp;gt;?.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Looks good! Now we have again “0x0f” at offset “0x20”.&lt;/p&gt;

&lt;p&gt;If we now parse the EDID data again:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cat /sys/bus/i2c/devices/7-0050/eeprom|parse-edid
Checksum Correct

Section &quot;Monitor&quot;
	Identifier &quot;W240D DVI&quot;
	ModelName &quot;W240D DVI&quot;
	VendorName &quot;HIT&quot;
	# Monitor Manufactured week 30 of 2007
	# EDID version 1.3
	# Digital Display
	DisplaySize 520 320
	Gamma 2.20
	Option &quot;DPMS&quot; &quot;false&quot;
	Horizsync 30-83
	VertRefresh 59-76
	# Maximum pixel clock is 180MHz
	#Not giving standard mode: 1920x1200, 60Hz
	#Not giving standard mode: 1600x1200, 60Hz
	#Not giving standard mode: 1680x1050, 60Hz
	#Not giving standard mode: 1280x1024, 60Hz

	#Extension block found. Parsing...
	Modeline 	&quot;Mode 11&quot; -hsync -vsync 
	Modeline 	&quot;Mode 0&quot; +hsync -vsync 
	Modeline 	&quot;Mode 1&quot; +hsync +vsync 
	Modeline 	&quot;Mode 2&quot; 148.500 1920 2008 2052 2200 1080 1084 1089 1125 +hsync +vsync
	Modeline 	&quot;Mode 3&quot; 74.250 1920 2448 2492 2640 1080 1082 1089 1125 +hsync +vsync interlace
	Modeline 	&quot;Mode 4&quot; 74.250 1280 1720 1760 1980 720 725 730 750 +hsync +vsync
	Modeline 	&quot;Mode 5&quot; 27.000 720 732 796 864 576 581 586 625 -hsync -vsync
	Modeline 	&quot;Mode 6&quot; 74.250 1920 2008 2052 2200 1080 1082 1087 1125 +hsync +vsync interlace
	Modeline 	&quot;Mode 7&quot; 74.250 1280 1390 1420 1650 720 725 730 750 +hsync +vsync
	Modeline 	&quot;Mode 8&quot; 27.027 720 736 798 858 480 489 495 525 -hsync -vsync
	Modeline 	&quot;Mode 9&quot; 27.027 720 736 798 858 480 489 495 525 -hsync -vsync
	Modeline 	&quot;Mode 10&quot; 25.200 640 656 752 800 480 490 492 525 -hsync -vsync
	Modeline 	&quot;Mode 12&quot; +hsync +vsync interlace
	Modeline 	&quot;Mode 13&quot; +hsync +vsync 
	Modeline 	&quot;Mode 14&quot; -hsync -vsync 
	Option &quot;PreferredMode&quot; &quot;Mode 11&quot;
EndSection
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we get again “Checksum Correct”.&lt;/p&gt;

&lt;h3 id=&quot;testing&quot;&gt;Testing&lt;/h3&gt;

&lt;p&gt;Ok, next step: Replug display cable. Then something unexpected happend: The monitor didn’t display anything at
all. In the OSD (on screen display) menu, it said the resolution was “1921x1200”, which doesn’t work of course.&lt;/p&gt;

&lt;p&gt;I simply chose the option “recall” in the OSD, which I think is kind of a reset to factory defaults. And voila -
the monitor works again.&lt;/p&gt;

&lt;h3 id=&quot;cleanup&quot;&gt;Cleanup&lt;/h3&gt;

&lt;p&gt;Since writing to i2c devices can be dangerous, it’s better to unload the i2c-dev module again afterwards:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ modprobe -r i2c-dev
$ modprobe -r eeprom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;further-thoughts&quot;&gt;Further thoughts&lt;/h2&gt;

&lt;p&gt;According to the specification in wikipedia of &lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_Display_Identification_Data&quot;&gt;Extended Display Identification Data&lt;/a&gt; the wrong byte at offset 0x20 is part of the chromatic values. This means,
that this broken EDID was probably an accident, as why would the HDMI stick change this? I’ve read, that sometimes
these EEPROMs can be messed up, when the monitor is started or plugged in or out. So it’s just bad luck.&lt;/p&gt;

&lt;p&gt;The original source was this &lt;a href=&quot;https://wiki.debian.org/RepairEDID&quot;&gt;EDID Repair&lt;/a&gt; which mentions another project:
&lt;a href=&quot;https://hackaday.io/project/18634-edid-inserter&quot;&gt;EDID Inserter&lt;/a&gt;. This sounds like an interesting project. In
the office, I also use a KVM switch and sometimes it takes time until the monitor shows an image. Sometimes the
monitor is not detected at all. This might be an explanation: When the KVM switch changes the display, it might
disconnect also the DDC (Display Data Channel) which transports the EDID. And this inserter tries to solve
the problem by connecting a fixed EEPROM directly so that the graphics card also sees a monitor connected and
doesn’t switch the video mode.&lt;/p&gt;

&lt;p&gt;Today, there seem to be commercial solutions available, just search for “EDID Emulator”. I would need such
a thing for Display Port (not HDMI), but that’s maybe not so easy, as the i2c lines are maybe not directly
exposed, see &lt;a href=&quot;https://www.ddcutil.com/displayport/&quot;&gt;DDC over Display Port&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are maybe other solutions possible - at least for linux. Search for “Linux Bypass EDID” or
&lt;a href=&quot;https://www.kernel.org/doc/Documentation/EDID/HOWTO.txt&quot;&gt;https://www.kernel.org/doc/Documentation/EDID/HOWTO.txt&lt;/a&gt; and look at this very old question
&lt;a href=&quot;https://askubuntu.com/questions/201081/how-can-i-make-linux-behave-better-when-edid-is-unavailable&quot;&gt;How can I make Linux behave better when EDID is unavailable?&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Maybe it’s worth a try?&lt;/p&gt;
</description>
        <pubDate>Sun, 26 Jan 2025 00:00:00 +0100</pubDate>
        <link>https://adangel.org/2025/01/26/monitor-edid-repair/</link>
        <guid isPermaLink="true">https://adangel.org/2025/01/26/monitor-edid-repair/</guid>
        
        <category>repair</category>
        
        <category>i2c</category>
        
        <category>eeprom</category>
        
        
      </item>
    
      <item>
        <title>iCal and Google Calendar synchronization</title>
        <description>&lt;p&gt;This post is a about a small detail I learned while investigating, why the changes in my (self written) ical
calendar export didn’t synchronize to Google calendar. In other words, the changes to an event, e.g. the updated
start/end times or repeating options didn’t show up on Google.&lt;/p&gt;

&lt;h2 id=&quot;the-problem&quot;&gt;The problem&lt;/h2&gt;

&lt;p&gt;So, caching definitely plays a role in that. Google itself states that changes to the imported ical calendar don’t
show up immediately, but take up to 12 hours. Actually, I only find this statement in a community post
&lt;a href=&quot;https://support.google.com/calendar/thread/121488828/intergrated-ical-calendar-not-syncing-in-8-12-hours?hl=en&quot;&gt;Intergrated iCal calendar not syncing in 8 - 12 hours&lt;/a&gt;
but not in the official documentation. But, that’s fine.&lt;/p&gt;

&lt;p&gt;The scenario is, that I added a calendar via URL. This URL is a &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc5545.html&quot;&gt;RFC 5545&lt;/a&gt;
compliant iCalendar resource that returns a couple of events, including some recurring events. The resource
is valid according to the validator on &lt;a href=&quot;https://icalendar.org/validator.html&quot;&gt;icalendar.org&lt;/a&gt;. And indeed, the
very first import in Google calendar looked correct. At some point, I noticed that new events didn’t appear
anymore. The reason was, that I got the folding wrong (for the description) and the actual resource was invalid and
therefore Google calendar didn’t add the new events. It just ignored the whole ical resource.
Once I fixed this the new events started to appear again after a while. So far so good.&lt;/p&gt;

&lt;p&gt;But some time later, I noticed, that a specific change to an event didn’t show up. The change was a different recurrent
rule, so it was obvious that something was not working: The old event was from Monday to Thursday. But it has been
changed to repeat weekly now from Monday to Friday. And the Friday calendar entry was missing in the
Google calendar. Which is pretty obvious in the weekly view.&lt;/p&gt;

&lt;h2 id=&quot;caching&quot;&gt;Caching?&lt;/h2&gt;

&lt;p&gt;I checked the access logs and there is a regular entry from a User Agent called “Google-Calendar-Importer”
that accesses the calendar. The interval was between 4 hours and 7 hours. So, really within the boundary
of 8-12 hours mentioned above. But I double checked, whether I return any caching related HTTP headers -
and I didn’t. So in theory, it could very well be the case, that the whole resource is somehow cached.
Although then I should see any access at all. But just to be sure, I added the headers &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Last-Modified&lt;/code&gt; and
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cache-Control: max-age=3600, must-revalidate&lt;/code&gt;. That means, it can be cached, but it needs to be revalidated
on every request (e.g. via a HEAD request). If the last modification timestamp didn’t change, the cached value
can be used. I also implemented the possibility to execute a HEAD request, which wasn’t possible before.&lt;/p&gt;

&lt;p&gt;I experimented a bit: When I change the URL and add some query parameter, in theory I should by-pass all
caching infrastructure. A GET request with a query-parameter shouldn’t be cached at all. And indeed, this
worked: The imported events from that modified URL appeared correctly.&lt;/p&gt;

&lt;p&gt;I also tried to remove the subscription and add it back again (with the same URL). But that didn’t show
success: It appeared the same way as before - the one event with the changed recurrent rule was still
the old version. All other events showed up as before. I have even seen a fresh request in the access log.
That means, that Google calendar actually requested the calender, but didn’t use it? Because there is another
cache going on?&lt;/p&gt;

&lt;h2 id=&quot;caching-single-events-change-management&quot;&gt;Caching single events… change management&lt;/h2&gt;

&lt;p&gt;It took me a while to notice that Google calendar seems to cache at the level of a single event. Clearly
the calendar is requested and downloaded. New events appear in the calendar, but previously existing events
are not updated.&lt;/p&gt;

&lt;p&gt;That led to back to the specification again for iCalendar. There is actually a section on
&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc5545.html#section-3.8.7&quot;&gt;3.8.7 Change Management Properties&lt;/a&gt;. This
explained a lot: For each event, a couple of properties can be defined, such as “CREATED” (when
the event was created), “DTSTAMP” (a timestamp of creation or last revision), “LAST-MODIFIED”
(when the event was last revised). And lastly “SEQUENCE” - which is kind of a incrementing version counter
for every revision of the event.&lt;/p&gt;

&lt;p&gt;I checked, which of these properties I change when the event is changed. And first I only changed the
“LAST-MODIFIED” time stamp. I kept the “DTSTAMP” the same, since I interpreted the specification in that
way, that this time stamp says when this event was first published in the calendar (the METHOD I used is publish…).
I experimented with that to use the same value as LAST-MODIFIED (since it could be, that the Google devs interpreted
this section of the specification in a different way), but that didn’t work. But I still now return the last
modification time stamp for that.&lt;/p&gt;

&lt;p&gt;The last property is “SEQUENCE” and I implemented it that way, that I always returned “0”. And I never
returned anything else. So, as last step, I implemented a simple change counter that gets incremented
whenever the event is modified in some way. And - lo and behold - it immediately started working. Immediately
meaning the next time Google calendar synchronized, which was a couple of hours later. But then, the event
suddenly appeared additionally on Friday, as it was intended.&lt;/p&gt;

&lt;p&gt;Note, that Google Calendar seems to manage the events based on a combination of the URL of the imported calendar and
the event’s &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc5545.html#section-3.8.4.7&quot;&gt;UID&lt;/a&gt; (Unique Identifier). That means,
you really need to make sure, that the UID of an event doesn’t change and is stable, but still unique.
Otherwise you might see duplicated events or you override existing events during import.&lt;/p&gt;

&lt;p&gt;I would also expect, that this caching/event management that Google Calendar does in the background is actually
independent of the current user’s calendar: If two (or more) users import a calendar from the same URL, they
would need to import this calendar only once. But that’s just an idea and I didn’t try to verify this.
If that’s true, then - in theory - you shouldn’t see additional access requests on the URL if more
Google users import that calendar into their own. As Google would then do only one request for all users.&lt;/p&gt;

&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;

&lt;p&gt;This means, that maybe some of the complaints about Google calendar not syncing in time or never syncing like
&lt;a href=&quot;https://support.google.com/calendar/thread/12658899/google-calendar-does-not-sync-url-linked-calendars-within-12-hours-as-stated?hl=en&quot;&gt;Google Calendar does not sync URL-linked calendars within 12 hours as stated&lt;/a&gt;
or &lt;a href=&quot;https://help.cheqroom.com/en/articles/1502123-why-is-my-google-calendar-sync-slow-or-not-updating-at-all&quot;&gt;Why is my Google Calendar sync slow or not updating at all?&lt;/a&gt;
or &lt;a href=&quot;https://support.google.com/calendar/thread/121488828/intergrated-ical-calendar-not-syncing-in-8-12-hours?hl=en&quot;&gt;Intergrated iCal calendar not syncing in 8 - 12 hours&lt;/a&gt;
might actually be a result of a not compliant iCalendar source, that doesn’t properly maintain the change
management properties. And Google Calendar might actually just work correctly.&lt;/p&gt;

&lt;p&gt;There is of course one challenge still open, that’s asked here:
&lt;a href=&quot;https://support.google.com/calendar/thread/83073961/how-do-i-adjust-sync-frequency-in-google-calendar?hl=en&quot;&gt;How do I adjust sync frequency in google calendar?&lt;/a&gt;. When you import a iCalender via URL, you have no control over how often Google Calender checks for updated / new
events. If it is not so time critical, then the stated “8-12 hours” time frame might be ok. If not, there
is another solution available as &lt;a href=&quot;https://github.com/derekantrican/GAS-ICS-Sync&quot;&gt;GAS-ICS-Sync&lt;/a&gt;. This is a script
that runs on Google’s &lt;a href=&quot;https://script.google.com&quot;&gt;App Script&lt;/a&gt;. I can be configured to run at an arbitrary interval
and it actively downloads the iCalendar resources and uses &lt;a href=&quot;https://developers.google.com/calendar/api/guides/overview?hl=en&quot;&gt;Google Calendar API&lt;/a&gt;
to add/update calendar events.&lt;/p&gt;

&lt;p&gt;For me, it was enough to fix my custom built iCalendar generator to correctly export the events
with the correct property &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SEQUENCE&lt;/code&gt;.&lt;/p&gt;
</description>
        <pubDate>Sun, 15 Dec 2024 00:00:00 +0100</pubDate>
        <link>https://adangel.org/2024/12/15/ical-and-google-calendar-sync/</link>
        <guid isPermaLink="true">https://adangel.org/2024/12/15/ical-and-google-calendar-sync/</guid>
        
        <category>ical</category>
        
        <category>calendar</category>
        
        
      </item>
    
  </channel>
</rss>
