Christos Margiolis: technology Christos Margiolis https://margiolis.net/tags/technology/ Configuring Gmail and Outlook365 on Neomutt https://margiolis.net/w/mutt_gmail_outlook/ Tue, 18 Mar 2025 00:00:00 +1200 <h2 id="gmail">Gmail</h2> <p>First enable two-factor authentication on your <a href="https://myaccount.google.com/signinoptions/twosv">Google account settings</a>, and then create an <a href="https://myaccount.google.com/apppasswords">application password</a> for <a href="https://neomutt.org/">Neomutt</a>.</p> <p>Once you have the app password, you can store it in your password manager <del>(or expose it in plain-text)</del>. I personally use <a href="https://www.passwordstore.org/">pass</a>, which is what my example configurations below also use:</p> <pre tabindex="0"><code>$ pass add gmail </code></pre><p>Create <code>~/.config/mutt/yourname@gmail.com</code> [1]:</p> <pre tabindex="0"><code>set from = &#34;Your Name &lt;yourname@gmail.com&gt;&#34; set realname = &#34;Your Name&#34; set imap_user = &#34;yourname@gmail.com&#34; set smtp_url = &#34;smtps://yourname@gmail.com@smtp.gmail.com:465/&#34; set folder = &#34;imaps://imap.gmail.com:993/&#34; set smtp_authenticators = &#34;gssapi:login&#34; set ssl_starttls = yes set ssl_force_tls = yes set my_pass = &#34;`pass gmail`&#34; set imap_pass = $my_pass set smtp_pass = $my_pass set spoolfile = &#34;+INBOX&#34; set record = &#34;+[Gmail]/Sent Mail&#34; set postponed = &#34;+[Gmail]/Drafts&#34; set trash = &#34;+[Gmail]/Trash&#34; mailboxes =INBOX =&#34;[Gmail]/Sent Mail&#34; =[Gmail]/Drafts =[Gmail]/Spam =[Gmail]/Trash </code></pre><p>Source the email configuration in <code>~/.config/mutt/muttrc</code>:</p> <pre tabindex="0"><code>source ~/.config/mutt/yourname@gmail.com folder-hook &#34;yourname@gmail.com&#34; &#34;source ~/.config/mutt/yourname@gmail.com&#34; </code></pre><h2 id="outlook365">Outlook365</h2> <p>Add your Outlook password to your password manager:</p> <pre tabindex="0"><code>$ pass add outlook </code></pre><p>Generate an OAuth2 token [2]. The <code>client-id</code> we are using here is Thunderbird&rsquo;s:</p> <pre tabindex="0"><code>$ /usr/local/share/neomutt/oauth2/mutt_oauth2.py \ -v \ -t \ --authorize \ --client-id &#34;9e5f94bc-e8a4-4e73-b8be-63364c29d753&#34; \ --client-secret &#34;&#34; \ --email &#34;yourname@outlook.com&#34; \ --provider microsoft \ ~/.config/mutt/outlooktoken </code></pre><p>When prompted, choose <code>authcode</code> as the &ldquo;OAuth2 flow&rdquo;. As for &ldquo;client secret&rdquo;, leave it empty. The script should then point you to a URL which will take you to the Outlook365 login page. Onced logged in, you will be redirected to a URL which includes the authorization code needed by the script. Because it might be hard to filter out the code by hand, you can copy the whole URL and run the following command to get the code:</p> <pre tabindex="0"><code>$ echo &#34;the_url&#34; | sed &#39;s/.*code=//;s/\&amp;.*//&#39; </code></pre><p>Paste the output of this command to the script prompt when asked, and press enter.</p> <p>Create <code>~/.config/mutt/yourname@outlook.com</code> [1]:</p> <pre tabindex="0"><code>set from = &#34;Your Name &lt;yourname@outlook.com&gt;&#34; set realname = &#34;Your Name&#34; set imap_user = &#34;yourname@outlook.com&#34; set smtp_url = &#34;smtp://yourname@outlook.com@smtp.office365.com:587&#34; set folder = &#34;imaps://yourname@outlook.com@outlook.office365.com&#34; set smtp_authenticators = &#34;xoauth2&#34; set imap_authenticators = &#34;xoauth2&#34; set ssl_starttls = yes set ssl_force_tls = yes set my_pass = &#34;`pass outlook`&#34; set imap_pass = $my_pass set smtp_pass = $my_pass set imap_oauth_refresh_command = &#34;/usr/local/share/neomutt/oauth2/mutt_oauth2.py ~/.config/mutt/outlooktoken&#34; set smtp_oauth_refresh_command = &#34;/usr/local/share/neomutt/oauth2/mutt_oauth2.py ~/.config/mutt/outlooktoken&#34; set spoolfile = &#34;+INBOX&#34; set record = &#34;+Sent Items&#34; set postponed = &#34;+Drafts&#34; set trash = &#34;+Deleted Items&#34; mailboxes =INBOX =&#34;Sent Items&#34; =Drafts =&#34;Deleted Items&#34; </code></pre><p>Source the email configuration in <code>~/.config/mutt/muttrc</code>:</p> <pre tabindex="0"><code>source ~/.config/mutt/yourname@outlook.com folder-hook &#34;yourname@outlook.com&#34; &#34;source ~/.config/mutt/yourname@outlook.com&#34; </code></pre><h2 id="notes">Notes</h2> <ol> <li>If you want to add more mailboxes, you can append them to <code>mailboxes</code> like so: <code>=MailboxName</code>.</li> <li>The paths for <code>mutt_oauth2.py</code> might differ depending on the operating system. For example, on FreeBSD it&rsquo;s <code>/usr/local/share/neomutt/oauth2/mutt_oauth2.py</code>, but on Linux, it&rsquo;s usually <code>/usr/share/neomutt/oauth2/mutt_oauth2.py</code>.</li> </ol> PostgreSQL 15 and pgAdmin4 on FreeBSD https://margiolis.net/w/postgres/ Thu, 07 Sep 2023 00:00:00 +1200 <p>Tested on FreeBSD 13.2. This article is also mirrored on the <a href="https://wiki.freebsd.org/PostgreSQL/Setup">FreeBSD Wiki</a>.</p> <h2 id="postgresql">PostgreSQL</h2> <p>Install PostgreSQL:</p> <pre tabindex="0"><code># pkg install postgresql15-server postgresql15-client </code></pre><p>Enable the PostgreSQL service:</p> <pre tabindex="0"><code># sysrc postgresql_enable=&#34;YES&#34; </code></pre><p>Initialize PostgreSQL and start the service:</p> <pre tabindex="0"><code># /usr/local/etc/rc.d/postgresql initdb # service postgresql start </code></pre><p>Make sure the service is running properly on IPv4 and IPv6 port 5432:</p> <pre tabindex="0"><code>$ sockstat -46 | grep 5432 </code></pre><p>Set a password for the default <code>postgres</code> user:</p> <pre tabindex="0"><code># passwd postgres </code></pre><p>Create an owner user for your database:</p> <pre tabindex="0"><code># su - postgres $ createuser admin $ createdb foo_db -O admin </code></pre><p>Inside the <code>psql</code> prompt, set an encrypted password for <code>admin</code> and grant exclusive access to <code>foo_db</code> to the <code>admin</code> user:</p> <pre tabindex="0"><code>$ psql foo_db foo_db=# alter role admin with encrypted password &#39;yourpassword&#39;; foo_db=# grant all privileges on database foo_db to admin; foo_db=# exit $ exit </code></pre><p>Make PostgreSQL listen to all addresses, instead of just <code>localhost</code>.</p> <pre tabindex="0"><code># vi /var/db/postgres/data15/postgresql.conf </code></pre><p>Uncomment the <code>listen_addresses</code> line and set it to:</p> <pre tabindex="0"><code>listen_addresses = &#39;*&#39; </code></pre><p>Change the security settings:</p> <pre tabindex="0"><code># vi /var/db/postgres/data15/pg_hba.conf </code></pre><p>Go to the bottom of the file and set <code>trust</code> to <code>md5</code> for all existing entries:</p> <pre tabindex="0"><code># TYPE DATABASE USER ADDRESS METHOD # &#34;local&#34; is for Unix domain socket connections only local all all md5 # IPv4 local connections: host all all 127.0.0.1/32 md5 # IPv6 local connections: host all all ::1/128 md5 # Allow replication connections from localhost, by a user with the # replication privilege. local replication all md5 host replication all 127.0.0.1/32 md5 host replication all ::1/128 md5 </code></pre><p>Also, allow remote IPv4 and IPv6 connections to our new database as <code>admin</code> only:</p> <pre tabindex="0"><code># Allow remote connections to foo_db as admin host foo_db admin 0.0.0.0/0 md5 host foo_db admin ::/0 md5 </code></pre><p>Restart the service:</p> <pre tabindex="0"><code># serivce postgresql restart </code></pre><h2 id="pgadmin4">pgAdmin4</h2> <p>Install the needed packages:</p> <pre tabindex="0"><code># pkg install python py39-pip py39-virtualenv py39-sqlite3 ca_root_nss openjpeg rust </code></pre><p>Upgrade pip to the latest version:</p> <pre tabindex="0"><code>$ pip install --upgrade pip </code></pre><p>Initialize the <code>virtualenv</code> for pgAdmin4 in your home directory:</p> <pre tabindex="0"><code>$ cd $ virtualenv pgadmin4 </code></pre><p>Enter the <code>virtualenv</code> and install pgAdmin4. This will take a while:</p> <pre tabindex="0"><code>$ . pgadmin4/bin/activate (pgadmin4) $ pip install pgadmin4 </code></pre><p>Create a local config file:</p> <pre tabindex="0"><code>(pgadmin4) # mkdir -p /var/lib/pgadmin /var/log/pgadmin (pgadmin4) $ cp pgadmin4/lib/python3.9/site-packages/pgadmin4/config.py pgadmin4/lib/python3.9/site-packages/pgadmin4/config_local.py (pgadmin4) $ vi pgadmin4/lib/python3.9/site-packages/pgadmin4/config_local.py </code></pre><p>Inside <code>config_local.py</code>, edit the following values:</p> <pre tabindex="0"><code>DEFAULT_SERVER = &#39;0.0.0.0&#39; DEFAULT_SERVER_PORT = 5050 </code></pre><p>Start pgAdmin4 (always inside the <code>virtualenv</code>) and set up your account:</p> <pre tabindex="0"><code>(pgadmin4) # pgadmin4 Email address: you@mail.com Password: yourpassword Retype password: yourpassword </code></pre><p>Open a web browser and enter pgAdmin4 (replace the IP with the appropriate one) and log in with the email and password you just entered in the <code>pgadmin4</code> prompt:</p> <pre tabindex="0"><code>http://192.168.1.11:5050 </code></pre><p>Inside the web interface, go to <code>File -&gt; Preferences -&gt; Binary Paths</code> and under the <code>PostgreSQL Binary Path</code> section, check <code>PostgreSQL 15</code> and set the binary path to <code>/usr/local/bin/</code>. Then click <code>Save</code>.</p> <p>From now on, you can run <code>pgadmin4</code> in the background like so:</p> <pre tabindex="0"><code>(pgadmin4) # pgadmin4 &amp; </code></pre><h3 id="upgrading-pgadmin4">Upgrading pgAdmin4</h3> <p>To upgrade pgAdmin4, you only have to delete and reinstall the <code>virtualenv</code>:</p> <pre tabindex="0"><code>$ cd $ rm -rf pgadmin4 $ virtualenv pgadmin4 $ . pgadmin4/bin/activate (pgadmin4) $ pip install --upgrade pgadmin4 </code></pre><p>You also have to re-create <code>config_local.py</code> and set <code>DEFAULT_SERVER</code> (see above).</p> Inline function tracing with the kinst DTrace provider https://margiolis.net/w/kinst_inline/ Tue, 18 Jul 2023 00:00:00 +1200 <h2 id="table-of-contents">Table of Contents</h2> <ol> <li><a href="#quick-background">Quick background</a></li> <li><a href="#usage">Usage</a></li> <li><a href="#inline-function-tracing">Inline function tracing</a> <ul> <li><a href="#how-it-works">How it works</a></li> <li><a href="#syntactic-transformations">Syntactic transformations</a></li> <li><a href="#heuristic-entry-return">Heuristic for calculating the <code>entry</code> and <code>return</code> offsets</a></li> </ul> </li> </ol> <h2 id="quick-background">Quick background</h2> <p><a href="https://illumos.org/books/dtrace/preface.html">DTrace</a> is a framework that gives administrators and kernel developers the ability to observe kernel behavior in real time. DTrace has modules called &ldquo;providers&rdquo;, that perform a particular instrumentation in the kernel (and sometimes userland) using &ldquo;probes&rdquo;.</p> <p><a href="https://margiolis.net/files/kinst.pdf">kinst</a> is a new low-level DTrace provider co-authored by Christos Margiolis and Mark Johnston for the FreeBSD operating system, which allows the user to trace arbitrary instructions in kernel functions. It is part of the base system as of FreeBSD 14.0.</p> <p>kinst probes take the form of <code>kinst::&lt;function&gt;:&lt;instruction&gt;</code>, where <code>&lt;function&gt;</code> is the kernel function to be traced, and <code>&lt;instruction&gt;</code> is the offset to the instruction, relative to the beginning of the function, and can be obtained from the function&rsquo;s disassembly. If the <code>&lt;instruction&gt;</code> field is left empty, kinst will trace all instructions in that function. Unlike <a href="https://illumos.org/books/dtrace/chp-fbt.html">FBT</a>, kinst can also trace the entry and return points of inline functions (see <a href="#inline-function-tracing">Inline function tracing</a>).</p> <p>The origin of the name is inspired from an <a href="https://www.usenix.org/legacy/publications/library/proceedings/osdi99/full_papers/tamches/tamches.pdf">early paper written by A. Tamches and B. Miller</a> discussing a tracing tool they developed called &ldquo;KernInst&rdquo;.</p> <h2 id="usage">Usage</h2> <p>Find the offset corresponding to the third instruction in <code>vm_fault()</code> and trace it, printing the contents of the RSI register:</p> <pre tabindex="0"><code># kgdb (kgdb) disas /r vm_fault Dump of assembler code for function vm_fault: 0xffffffff80f4e470 &lt;+0&gt;: 55 push %rbp 0xffffffff80f4e471 &lt;+1&gt;: 48 89 e5 mov %rsp,%rbp 0xffffffff80f4e474 &lt;+4&gt;: 41 57 push %r15 ... # dtrace -n &#39;kinst::vm_fault:4 {printf(&#34;%#x&#34;, regs[R_RSI]);}&#39; 2 81500 vm_fault:4 0x827c56000 2 81500 vm_fault:4 0x827878000 2 81500 vm_fault:4 0x1fab9bef0000 2 81500 vm_fault:4 0xe16cf749000 0 81500 vm_fault:4 0x13587c366000 ^C </code></pre><p>Trace the return point of <code>critical_enter()</code>, which is an inline function:</p> <pre tabindex="0"><code># dtrace -n &#39;kinst::critical_enter:return&#39; dtrace: description &#39;kinst::critical_enter:return&#39; matched 130 probes CPU ID FUNCTION:NAME 1 71024 spinlock_enter:53 0 71024 spinlock_enter:53 1 70992 uma_zalloc_arg:49 1 70925 malloc_type_zone_allocated:21 1 70994 uma_zfree_arg:365 1 70924 malloc_type_freed:21 1 71024 spinlock_enter:53 0 71024 spinlock_enter:53 0 70947 _epoch_enter_preempt:122 0 70949 _epoch_exit_preempt:28 ^C </code></pre><h2 id="inline-function-tracing">Inline function tracing</h2> <h3 id="how-it-works">How it works</h3> <p>To trace inline functions, libdtrace makes use of the <a href="https://margiolis.net/w/dwarf_inline">DWARF Debugging Standard</a>, to detect if the function specified is an inline call. If it is, D syntax is transformed to create kinst probes for each of the inline copies found. All work is done in libdtrace, instead of kinst(4). This feature has been added to FreeBSD with <a href="https://reviews.freebsd.org/D38825">this patch</a>.</p> <p>Contrary to how kinst expects a <code>&lt;function&gt;:&lt;instruction&gt;</code> tuple to create probes, for inline functions, <code>&lt;instruction&gt;</code> is replaced by <code>entry</code> and <code>return</code>.</p> <h3 id="syntactic-transformations">Syntactic transformations</h3> <p>Suppose the user wants to trace a probe of the form:</p> <pre tabindex="0"><code>kinst::&lt;func&gt;:&lt;entry|return&gt; /&lt;pred&gt;/ { &lt;acts&gt; } </code></pre><p>libdtrace sees that we have specified <code>entry</code> or <code>return</code>, instead of an offset, which is what a regular kinst probe would look like, so it loops through all loaded kernel modules and parses their DWARF and ELF info to see if this function is an inline &mdash; if <em>not</em>, the probe is converted to an FBT one, so that we don&rsquo;t duplicate FBT&rsquo;s functionality in kinst:</p> <pre tabindex="0"><code># dtrace -dn &#39;kinst::malloc:entry {exit(0);}&#39; fbt::malloc:entry { exit(0x0); } dtrace: description &#39;kinst::malloc:entry &#39; matched 1 probe CPU ID FUNCTION:NAME 2 31144 malloc:entry </code></pre><p>If the function however <em>is</em> an inline, libdtrace will find all calls refering to this function and create new probes for each one of the inline copies found.</p> <pre tabindex="0"><code># dtrace -dn &#39;kinst::cam_iosched_has_more_trim:entry { printf(&#34;\t%d\t%s&#34;, pid, execname); }&#39; kinst::cam_iosched_get_trim:13, kinst::cam_iosched_next_bio:13, kinst::cam_iosched_schedule:40 { printf(&#34;\t%d\t%s&#34;, pid, execname); } dtrace: description &#39;kinst::cam_iosched_has_more_trim:entry &#39; matched 4 probes CPU ID FUNCTION:NAME 0 81502 cam_iosched_schedule:40 2 clock 0 81501 cam_iosched_next_bio:13 2 clock 2 81502 cam_iosched_schedule:40 2 clock 1 81502 cam_iosched_next_bio:13 0 kernel 1 81503 cam_iosched_schedule:40 0 kernel ^C </code></pre><p>There can also be both inline and non-inline definitions of the same function. In this case, kinst creates an additional FBT probe for the non-inline definition.</p> <p>The <code>-d</code> flag used in these examples to dump the D script after libdtrace has applied syntactic transformations, has been added to DTrace in <a href="https://cgit.freebsd.org/src/commit/?id=1e136a9cbd3a9d137037e47a53c1dba3be7f6925">commit 1e136a9cbd3a</a>.</p> <h3 id="heuristic-entry-return">Heuristic for calculating the <code>entry</code> and <code>return</code> offsets</h3> <p>libdtrace reuses parts of the mechanism implemented in my <a href="https://git.sr.ht/~crm/inlinecall">inlinecall(1)</a> program, which finds and prints all call sites of a given inline function:</p> <pre tabindex="0"><code>$ ./inlinecall vm_page_mvqueue /usr/lib/debug/boot/kernel/kernel.debug /usr/src/sys/vm/vm_page.c:4142 [0xffffffff80f91541 - 0xffffffff80f91599] /usr/src/sys/vm/vm_page.c:4195 vm_page_readahead_finish() [0xffffffff80f915f5 - 0xffffffff80f91603] /usr/src/sys/vm/vm_page.c:4195 vm_page_readahead_finish() [0xffffffff80f9163d - 0xffffffff80f916c2] /usr/src/sys/vm/vm_page.c:4184 vm_page_activate() [0xffffffff80f916cd - 0xffffffff80f916e5] /usr/src/sys/vm/vm_page.c:4184 vm_page_activate() [0xffffffff80f916fe - 0xffffffff80f91747] /usr/src/sys/vm/vm_page.c:4195 vm_page_deactivate() [0xffffffff80f91750 - 0xffffffff80f91768] /usr/src/sys/vm/vm_page.c:4195 vm_page_deactivate() [0xffffffff80f94a59 - 0xffffffff80f94aa9] /usr/src/sys/vm/vm_page.c:4195 vm_page_reclaim_contig_domain() [0xffffffff80f94de4 - 0xffffffff80f94df9] /usr/src/sys/vm/vm_page.c:4195 vm_page_reclaim_contig_domain() [0xffffffff80f9661e - 0xffffffff80f96667] /usr/src/sys/vm/vm_page.c:4202 vm_page_deactivate_noreuse() [0xffffffff80f96670 - 0xffffffff80f96688] /usr/src/sys/vm/vm_page.c:4202 vm_page_deactivate_noreuse() [0xffffffff80f9669e - 0xffffffff80f966ea] /usr/src/sys/vm/vm_page.c:4212 vm_page_launder() [0xffffffff80f966f3 - 0xffffffff80f9670b] /usr/src/sys/vm/vm_page.c:4212 vm_page_launder() [0xffffffff80f96d4c - 0xffffffff80f96dac] /usr/src/sys/vm/vm_page.c:4212 vm_page_advise() [0xffffffff80f96dac - 0xffffffff80f96e07] /usr/src/sys/vm/vm_page.c:4202 vm_page_advise() </code></pre><p>Most of the entries above appear twice but with different boundaries:</p> <pre tabindex="0"><code> [0xffffffff80f9163d - 0xffffffff80f916c2] /usr/src/sys/vm/vm_page.c:4184 vm_page_activate() [0xffffffff80f916cd - 0xffffffff80f916e5] /usr/src/sys/vm/vm_page.c:4184 vm_page_activate() </code></pre><p>This means that the inline copy&rsquo;s boundaries are split into more than one parts, which can be caused by having early <code>return</code>s inside the inline function. By using this assumption, we can deduce that the entry point of the function is <code>0xffffffff80f9163d</code>, and each of the upper boundaries are the return points (i.e., <code>0xffffffff80f916c2</code> and <code>0xffffffff80f916e5</code>), so we end up with one <code>entry</code> and two <code>return</code> trace points.</p> <p>However, this is <strong>not exactly true</strong>, because the final return address given by DWARF corresponds to the instruction <em>after</em> the actual return instruction. We can verify this in GDB using the two return addresses from the example above:</p> <pre tabindex="0"><code># kgdb (kgdb) disas vm_page_activate ... 0xffffffff80f916c0 &lt;+144&gt;: jmp 0xffffffff80f91673 &lt;vm_page_activate+67&gt; 0xffffffff80f916c2 &lt;+146&gt;: add $0x8,%rsp &lt;-- first address given by DWARF ... 0xffffffff80f916e0 &lt;+176&gt;: call 0xffffffff80bed360 &lt;panic&gt; &lt;-- last instruction &lt;-- second address should be here </code></pre><p>It turns out that <code>0xffffffff80f916e5</code> is, in fact, outside <code>vm_page_activate()</code> altogether!</p> <pre tabindex="0"><code>(kgdb) x/i 0xffffffff80f916e5 0xffffffff80f916e5: data16 cs nopw 0x0(%rax,%rax,1) </code></pre><p>After running a couple of tests, I came to the conclusion that there are two possible cases with return addresses:</p> <ul> <li>If <a href="https://margiolis.net/w/dwarf_inline/#lowpc-highpc">the inline copy&rsquo;s DIE has <code>DW_AT_lowpc</code> and <code>DW_AT_highpc</code> set</a>, the return address is <em>always</em> outside the inline function&rsquo;s boundaries.</li> <li>If <a href="https://margiolis.net/w/dwarf_inline/#ranges">the inline copy&rsquo;s DIE has <code>DW_AT_ranges</code> set</a>, only the last return address is outside the inline function&rsquo;s boundaries.</li> <li>Combining the two bullets above, if the return address of the inline function is the same as the upper boundary of the caller function, it&rsquo;s outside <em>both</em> the inline copy&rsquo;s and the caller function&rsquo;s boundaries.</li> </ul> <p>In order to fix this, we have to go one instruction back whenever we come across one of those 3 cases.</p> <p>Finally the <code>entry</code> offset is calculated as:</p> <pre tabindex="0"><code>inline_bound_lo - caller_bound_lo </code></pre><p>And the <code>return</code> one, including the modifications (if any) discussed above, as:</p> <pre tabindex="0"><code>inline_bound_hi - caller_bound_lo </code></pre><p>These offsets are then used to create regular kinst probes of the form <code>kinst::&lt;func&gt;:&lt;instruction&gt;</code>, which is what kinst actually expects:</p> <pre tabindex="0"><code># dtrace -dn &#39;kinst::vm_page_mvqueue:entry,kinst::vm_page_mvqueue:return&#39; dtrace: description &#39;kinst::vm_page_mvqueue:entry,kinst::vm_page_mvqueue:return&#39; matched 22 probes kinst::vm_page_readahead_finish:33, kinst::vm_page_readahead_finish:121, kinst::vm_page_activate:13, kinst::vm_page_deactivate:14, kinst::vm_page_reclaim_contig_domain:1961, kinst::vm_page_deactivate_noreuse:14, kinst::vm_page_launder:14, kinst::vm_page_advise:236, kinst::vm_page_advise:332, kinst::vm_page_readahead_finish:220, kinst::vm_page_activate:146, kinst::vm_page_activate:176, kinst::vm_page_deactivate:87, kinst::vm_page_deactivate:115, kinst::vm_page_reclaim_contig_domain:2041, kinst::vm_page_reclaim_contig_domain:2884, kinst::vm_page_deactivate_noreuse:87, kinst::vm_page_deactivate_noreuse:115, kinst::vm_page_launder:90, kinst::vm_page_launder:118, kinst::vm_page_advise:330, kinst::vm_page_advise:421 { } CPU ID FUNCTION:NAME 3 95381 vm_page_activate:13 3 95389 vm_page_activate:146 2 95381 vm_page_activate:13 2 95389 vm_page_activate:146 1 95387 vm_page_advise:332 1 95400 vm_page_advise:421 1 95387 vm_page_advise:332 1 95400 vm_page_advise:421 1 95387 vm_page_advise:332 ^C </code></pre> Using DWARF to find call sites of inline functions https://margiolis.net/w/dwarf_inline/ Tue, 07 Feb 2023 00:00:00 +1200 <h2 id="table-of-contents">Table of contents</h2> <ol> <li><a href="#what-is-dwarf">What is DWARF?</a></li> <li><a href="#inline-functions-in-dwarf">Inline functions in DWARF</a></li> <li><a href="#calculating-call-boundaries">Calculating call boundaries</a> <ul> <li><a href="#lowpc-highpc">The DIE has <code>DW_AT_low_pc</code> and <code>DW_AT_high_pc</code></a></li> <li><a href="#ranges">The DIE has <code>DW_AT_ranges</code></a></li> </ul> </li> <li><a href="#finding-the-caller-function">Finding the caller function</a></li> <li><a href="#inlinecall">inlinecall(1)</a> <ul> <li><a href="#nested-inline-functions">Nested inline functions</a></li> </ul> </li> </ol> <h2 id="what-is-dwarf">What is DWARF?</h2> <p>From the <a href="https://dwarfstd.org/">DWARF Debugging Standard&rsquo;s documentation</a>:</p> <blockquote> <p>This document defines a format for describing programs to facilitate user source level debugging. This description can be generated by compilers, assemblers and linkage editors. It can be used by debuggers and other tools.</p></blockquote> <p>Debugging information entries (DIEs) are represented as a tree, one per compilation unit (CU). Each DIE has a tag (<code>DW_TAG_*</code>, see DWARF PDF Figure 1) denoting its class, and attributes (<code>DW_AT_*</code>, see DWARF PDF Figure 2) denoting its various characteristics, associated with it.</p> <p>The next entry of a DIE is a child DIE. If a DIE doesn&rsquo;t have children, the next entry is a &ldquo;sibling&rdquo;.</p> <p>Consider the following structure:</p> <pre tabindex="0"><code>CU1 (DW_TAG_compile_unit) func1 (DW_TAG_subprogram) DW_AT_foo DW_AT_bar func2 (DW_TAG_subprogram) DW_AT_foo DW_AT_bar myvar (DW_TAG_variable) DW_AT_foo DW_AT_bar CU2 (DW_TAG_compile_unit) ... </code></pre><p><code>CU1</code> has <code>func1</code> and <code>myvar</code> as chidren and <code>CU2</code> as siblings. <code>func1</code> has <code>func2</code> as a child.</p> <p>The debug file can be generated by compiling with the <code>-g</code> option. To dump DWARF info you can use <code>readelf -wi &lt;file&gt;</code> and <code>dwarfdump &lt;file&gt;</code>.</p> <h2 id="inline-functions-in-dwarf">Inline functions in DWARF</h2> <p>DIEs of inline function declarations have the <code>DW_TAG_subprogram</code> tag and the <code>DW_AT_inline</code> attribute. DIEs of inline copies of this function will have the <code>DW_TAG_inlined_subroutine</code> tag.</p> <p>Attributes inline copies can have include:</p> <ul> <li><code>DW_AT_abstract_origin</code>: DIE offset to the inline declaration.</li> <li><code>DW_AT_call_file</code>: Integer denoting the file the function is called in.</li> <li><code>DW_AT_call_line</code>: File line.</li> <li><code>DW_AT_call_column</code>: File column.</li> <li><code>DW_AT_low_pc</code> and <code>DW_AT_high_pc</code>: Lower and upper call boundaries. <a href="#3.1">Explained in the next section</a>.</li> <li><code>DW_AT_ranges</code>: <a href="#3.2">Explained in the next section</a>.</li> </ul> <p>For example, if we dump the DWARF info for my FreeBSD kernel:</p> <pre tabindex="0"><code>$ readelf -wi /usr/lib/debug/boot/kernel/kernel.debug &gt; ~/foo </code></pre><p>We find that <code>vfs_freevnodes_dec</code> gets inlined:</p> <pre tabindex="0"><code> &lt;1&gt;&lt;1dfa144&gt;: Abbrev Number: 94 (DW_TAG_subprogram) &lt;1dfa145&gt; DW_AT_name : (indirect string) vfs_freevnodes_dec &lt;1dfa149&gt; DW_AT_decl_file : 1 &lt;1dfa14a&gt; DW_AT_decl_line : 1447 &lt;1dfa14c&gt; DW_AT_prototyped : 1 &lt;1dfa14c&gt; DW_AT_inline : 1 </code></pre><p>Inline copies will have <code>DW_AT_abstract_origin</code> point to the declaration&rsquo;s DIEs offset, in this case <code>0x1dfa144</code>. If we look for <code>0x1dfa144</code>, we do indeed find a few inline copies.</p> <pre tabindex="0"><code> &lt;3&gt;&lt;1dfe45e&gt;: Abbrev Number: 24 (DW_TAG_inlined_subroutine) &lt;1dfe45f&gt; DW_AT_abstract_origin: &lt;0x1dfa144&gt; &lt;1dfe463&gt; DW_AT_low_pc : 0xffffffff80cf701d &lt;1dfe46b&gt; DW_AT_high_pc : 0x38 &lt;1dfe46f&gt; DW_AT_call_file : 1 &lt;1dfe470&gt; DW_AT_call_line : 3458 &lt;1dfe472&gt; DW_AT_call_column : 5 &lt;3&gt;&lt;1dfd2e2&gt;: Abbrev Number: 58 (DW_TAG_inlined_subroutine) &lt;1dfd2e3&gt; DW_AT_abstract_origin: &lt;0x1dfa144&gt; &lt;1dfd2e7&gt; DW_AT_ranges : 0x1f1290 &lt;1dfd2eb&gt; DW_AT_call_file : 1 &lt;1dfd2ec&gt; DW_AT_call_line : 3405 &lt;1dfd2ee&gt; DW_AT_call_column : 3 ...there are more </code></pre><p>As I described in the <a href="#1">first section</a>, a debug file may consist of multiple CUs that define the same inline function. We want treat each CU independently, that is, each inline copy is handled relative to its CU.</p> <h2 id="calculating-call-boundaries">Calculating call boundaries</h2> <p>There are 2 cases we have to take care of when calculating the actual call boundaries of an inline copy.</p> <h3 id="lowpc-highpc">The DIE has <code>DW_AT_low_pc</code> and <code>DW_AT_high_pc</code></h3> <pre tabindex="0"><code> &lt;3&gt;&lt;1dfe45e&gt;: Abbrev Number: 24 (DW_TAG_inlined_subroutine) &lt;1dfe45f&gt; DW_AT_abstract_origin: &lt;0x1dfa144&gt; &lt;1dfe463&gt; DW_AT_low_pc : 0xffffffff80cf701d &lt;1dfe46b&gt; DW_AT_high_pc : 0x38 &lt;1dfe46f&gt; DW_AT_call_file : 1 &lt;1dfe470&gt; DW_AT_call_line : 3458 &lt;1dfe472&gt; DW_AT_call_column : 5 </code></pre><p>In this case, the lower boundary is <code>low_pc</code> and the upper boundary is <code>low_pc + high_pc</code>, which, for the DIE shown in this example, the boundaries are:</p> <pre tabindex="0"><code>low = 0xffffffff80cf701d high = 0xffffffff80cf701d + 0x38 = 0xffffffff80cf7055 </code></pre><h3 id="ranges">The DIE has <code>DW_AT_ranges</code></h3> <pre tabindex="0"><code> &lt;3&gt;&lt;1dfd2e2&gt;: Abbrev Number: 58 (DW_TAG_inlined_subroutine) &lt;1dfd2e3&gt; DW_AT_abstract_origin: &lt;0x1dfa144&gt; &lt;1dfd2e7&gt; DW_AT_ranges : 0x1f1290 &lt;1dfd2eb&gt; DW_AT_call_file : 1 &lt;1dfd2ec&gt; DW_AT_call_line : 3405 &lt;1dfd2ee&gt; DW_AT_call_column : 3 </code></pre><p>This is a bit more involved. <code>DW_AT_ranges</code> refers to the <code>.debug_ranges</code> section found in debug files. We can dump the ranges:</p> <pre tabindex="0"><code>$ dwarfdump -N /usr/lib/debug/boot/kernel/kernel.debug .debug_ranges Ranges group 0: ranges: 3 at .debug_ranges offset 0 (0x00000000) (48 bytes) [ 0] range entry 0x00000019 0x00000073 [ 1] range entry 0x0000007e 0x00000106 [ 2] range end 0x00000000 0x00000000 Ranges group 1: ranges: 3 at .debug_ranges offset 48 (0x00000030) (48 bytes) [ 0] range entry 0x00000022 0x0000006a [ 1] range entry 0x0000007e 0x00000106 [ 2] range end 0x00000000 0x00000000 ... </code></pre><p>If we search for <code>0x1f1290</code> (the inline copy&rsquo;s ranges), we find its range group:</p> <pre tabindex="0"><code> Ranges group 38809: ranges: 3 at .debug_ranges offset 2036368 (0x001f1290) (48 bytes) [ 0] range entry 0x000025c8 0x000025f9 [ 1] range entry 0x0000261a 0x00002621 [ 2] range end 0x00000000 0x00000000 </code></pre><p>To get the call boundaries, we add each <code>range entry</code>&rsquo;s boundaries to the <code>DW_AT_low_pc</code> of the root DIE of the CU. The root DIE is found programmatically, but I happen to know that in this case, the root DIE is:</p> <pre tabindex="0"><code> &lt;0&gt;&lt;1dee9fb&gt;: Abbrev Number: 1 (DW_TAG_compile_unit) &lt;1dee9fc&gt; DW_AT_producer : (indirect string) FreeBSD clang version 13.0.0 (git@github.com:llvm/llvm-project.git llvmorg-13.0.0-0-gd7b669b3a303) &lt;1deea00&gt; DW_AT_language : 12 (C99) &lt;1deea02&gt; DW_AT_name : (indirect string) /usr/src/sys/kern/vfs_subr.c &lt;1deea06&gt; DW_AT_stmt_list : 0x6cb448 &lt;1deea0a&gt; DW_AT_comp_dir : (indirect string) /usr/obj/usr/src/amd64.amd64/sys/GENERIC &lt;1deea0e&gt; DW_AT_low_pc : 0xffffffff80cf4020 &lt;1deea16&gt; DW_AT_high_pc : 0xde3d </code></pre><p>Finally, we end up with the following boundaries:</p> <pre tabindex="0"><code>low = 0xffffffff80cf4020 + 0x000025c8 = 0xffffffff80cf65e8 high = 0xffffffff80cf4020 + 0x000025f9 = 0xffffffff80cf6619 low = 0xffffffff80cf4020 + 0x0000261a = 0xffffffff80cf663a high = 0xffffffff80cf4020 + 0x00002621 = 0xffffffff80cf6641 </code></pre><h2 id="finding-the-caller-function">Finding the caller function</h2> <p>There are cases where we want to know which function an inline function is being called from. Because DWARF does not encode that information, we&rsquo;ll have to scan ELF symbol tables.</p> <pre tabindex="0"><code>$ readelf -s /usr/lib/debug/boot/kernel/kernel.debug </code></pre><p>Since we know the inline copy&rsquo;s boundaries, we only have to find which symbol&rsquo;s boundaries the inline copy is inside. In other words, the following condition has to be met:</p> <pre tabindex="0"><code>sym_lower_bound &lt;= inline_lower_bound &lt;= inline_upper_bound &lt;= sym_upper_bound </code></pre><p>Because searching through ELF symbol tables manually and doing calculations by hand would take too long, the best way to do this is programmatically through <a href="https://sourceforge.net/p/elftoolchain/wiki/libelf/">LibELF</a>.</p> <h2 id="inlinecall">inlinecall(1)</h2> <p><a href="https://git.sr.ht/~crm/inlinecall">I wrote a little program</a> that does everything I talked about in this post automatically. It works on FreeBSD as-is, and most likely needs some modification to get it to work on other platforms.</p> <p>The program takes an inline function name and a debug file as arguments:</p> <pre tabindex="0"><code>inlinecall &lt;function&gt; &lt;file&gt; </code></pre><p>And outputs the results in the following form:</p> <pre tabindex="0"><code>cu1_func_declaration_file:line [low_bound - high_bound] inline_copy1_file:line caller_func() [low_bound - high_bound] inline_copy2_file:line caller_func() ... cu2_func_declaration_file:line ... ... </code></pre><p>For example:</p> <pre tabindex="0"><code>$ inlinecall critical_enter /usr/lib/debug/boot/kernel/kernel.debug /usr/src/sys/sys/systm.h:175 [0xffffffff809eb51f - 0xffffffff809eb526] /usr/src/sys/kern/kern_intr.c:1387 intr_event_handle() /usr/src/sys/sys/systm.h:175 [0xffffffff80a051f4 - 0xffffffff80a05208] /usr/src/sys/kern/kern_malloc.c:431 malloc_type_freed() [0xffffffff80a0514c - 0xffffffff80a0515b] /usr/src/sys/kern/kern_malloc.c:388 malloc_type_zone_allocated() /usr/src/sys/sys/systm.h:175 [0xffffffff80a263c4 - 0xffffffff80a263d3] /usr/src/sys/kern/kern_resource.c:509 rtp_to_pri() /usr/src/sys/sys/systm.h:175 [0xffffffff80a28f59 - 0xffffffff80a28f5f] /usr/src/sys/kern/kern_rmlock.c:775 _rm_assert() [0xffffffff80a29087 - 0xffffffff80a2908d] /usr/src/sys/kern/kern_rmlock.c:801 _rm_assert() [0xffffffff80a29eb0 - 0xffffffff80a29eb7] /usr/src/sys/kern/kern_rmlock.c:645 _rm_rlock_debug() [0xffffffff80a28c4b - 0xffffffff80a28c5a] /usr/src/sys/kern/kern_rmlock.c:160 unlock_rm() ...more </code></pre><h3 id="nested-inline-functions">Nested inline functions</h3> <p>inlinecall(1) resolves nested inline functions recursively:</p> <pre tabindex="0"><code>$ ./inlinecall critical_enter /usr/lib/debug/boot/kernel/kernel.debug /usr/src/sys/sys/systm.h:175 [0xffffffff80a19d7a - 0xffffffff80a19d8b] /usr/src/sys/sys/buf_ring.h:80 drbr_enqueue() /usr/src/sys/sys/systm.h:175 [0xffffffff80a6387a - 0xffffffff80a6388b] /usr/src/sys/sys/buf_ring.h:80 drbr_enqueue() ... </code></pre><p>Looking at the definition of <code>critical_enter()</code>&rsquo;s caller function in <code>buf_ring.h</code>:</p> <pre tabindex="0"><code>static __inline int buf_ring_enqueue(struct buf_ring *br, void *buf) { ... critical_enter(); ... } </code></pre><p>Even though inlinecall(1) reported that <code>critical_enter()</code> is called from <code>drbr_enqueue()</code> in <code>buf_ring.h:80</code>, we see that it&rsquo;s called from <code>buf_ring_enqueue()</code> instead, but <code>buf_ring_enqueue()</code> is also an inline function:</p> <pre tabindex="0"><code>$ ./inlinecall buf_ring_enqueue /usr/lib/debug/boot/kernel/kernel.debug /usr/src/sys/sys/buf_ring.h:63 [0xffffffff80a19d7a - 0xffffffff80a19dcd] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80a19ddc - 0xffffffff80a19e18] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80a19e1f - 0xffffffff80a19e3b] /usr/src/sys/net/ifq.h:337 drbr_enqueue() /usr/src/sys/sys/buf_ring.h:63 [0xffffffff80a6387a - 0xffffffff80a638cd] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80a638dc - 0xffffffff80a63918] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80a6391f - 0xffffffff80a6393b] /usr/src/sys/net/ifq.h:337 drbr_enqueue() /usr/src/sys/sys/buf_ring.h:63 [0xffffffff80d1f81a - 0xffffffff80d1f879] /usr/src/sys/net/ifq.c:57 drbr_enqueue() [0xffffffff80d1f91d - 0xffffffff80d1f964] /usr/src/sys/net/ifq.c:57 drbr_enqueue() [0xffffffff80d1f9dd - 0xffffffff80d1f9f5] /usr/src/sys/net/ifq.c:57 drbr_enqueue() /usr/src/sys/sys/buf_ring.h:63 [0xffffffff80ff07ba - 0xffffffff80ff080d] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80ff081c - 0xffffffff80ff0858] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80ff085f - 0xffffffff80ff087b] /usr/src/sys/net/ifq.h:337 drbr_enqueue() </code></pre><p>Here <code>drbr_enqueue()</code> is defined twice &mdash; once in <code>ifq.h</code> and once in <code>ifq.c</code>. The definition in <code>ifq.h</code> is also an inline definition, and in <code>ifq.c</code> it&rsquo;s a non-inline one. We know that <code>buf_ring_enqueue()</code> is called from the non-inline version of <code>drbr_enqueue()</code>, otherwise inlinecall(1) would have reported the function which calls the inline version of <code>drbr_enqueue()</code>.</p> In defense of the Old Web https://margiolis.net/w/oldweb/ Wed, 17 Aug 2022 00:00:00 +1200 <p>The internet has reached the point where almost everything has been concertrated in just a few platforms. This is both opposed to the original idea of a decentralized and free internet, and is also used as a <a href="https://margiolis.net/w/socialmedia">tool for control</a>, since the flow of information is &ldquo;managed&rdquo; only by a handful of companies. Another problem is the amount of AI-generated and SEO-optimized websites plaguing every search engine result nowadays, making it almost impossible to find quality handwritten individual websites anymore.</p> <p>My rather romanticized proposal is that we should make an attempt return to the old ways of the internet (1990s-2000s) &mdash; when personal websites and small communities were thriving &mdash; not because of nostalgia, but because it espoused better values and promoted creativity, despite its problems (spam, malware, bad security practices, unencrypted traffic). This might very well be a pipe dream at this point, but I think it would be great to at least see the existing movement attracting more people than it already does.</p> <p>I was initially working on a full article explaining my viewpoint in depth, but the following articles express them better than I could ever have:</p> <ul> <li><a href="https://neustadt.fr/essays/against-a-user-hostile-web/">Against an Increasingly User-Hostile Web</a></li> <li><a href="https://neustadt.fr/essays/the-small-web/">Rediscovering the Small Web</a></li> <li><a href="https://benhoyt.com/writings/the-small-web-is-beautiful/">The small web is beautiful</a></li> <li><a href="https://webdirections.org/blog/the-website-obesity-crisis/">The Website Obesity Crisis</a></li> <li><a href="https://stackingthebricks.com/how-blogs-broke-the-web/">How the Blog Broke the Web</a></li> </ul> <p>So, how can you be part of the small web? To start off, you need:</p> <ul> <li><a href="https://landchad.net/basic/domain/">A domain name</a>.</li> <li>Somewhere to host your website/service. I rent a <a href="https://vultr.com">Vultr</a> VPS with the cheapest plan available, which is more than enough for my needs, but you can even do self-hosting at home if you want. If you do rent from Vultr, you can <a href="https://landchad.net/basic/server/">follow this guide</a>.</li> <li>To <a href="https://landchad.net/basic/dns/">connect the domain name with the server</a>, so that your website can be reached.</li> <li>A stable and secure operating system for servers, such as <a href="https://www.freebsd.org/">FreeBSD</a> or <a href="https://openbsd.org">OpenBSD</a>.</li> </ul> <p>A list of guides I&rsquo;ve written:</p> <ul> <li><a href="https://margiolis.net/w/openbsd_web">Set up an OpenBSD web server</a>.</li> <li><a href="https://margiolis.net/w/rss">Create an RSS feed for your website</a>.</li> <li><a href="https://margiolis.net/w/rsync">Edit files locally and upload them to the server</a>.</li> <li><a href="https://margiolis.net/w/openbsd_git">Set up an OpenBSD Git server</a> with a mimimal <a href="https://margiolis.net/w/stagit_frontend">web frontend</a>.</li> </ul> <p>To make finding other websites easier, have a <a href="https://margiolis.net/links">links page</a>, and consider being part of a Webring.</p> <p>The tools you choose to manage your website depend on personal preference and needs, but I firmly believe that because building a website is not rocket science, and because <a href="https://idlewords.com/talks/website_obesity.htm">the web has been getting severely bloated</a> you don&rsquo;t need anything more than a few command line utilities, a text editor, and perhaps a <a href="https://staticsitegenerators.net/">static site generator</a>. Modern web <a href="https://dayssincelastjavascriptframework.com/">frameworks</a> tend to cause more headaches than actually improve workflow, so I&rsquo;ll refrain from recommending them. I like writing articles in <a href="https://daringfireball.net/projects/markdown/basics">Markdown</a>, and using <a href="https://gohugo.io/">Hugo</a> for static site generation and templating.</p> Making a character device kernel module on FreeBSD https://margiolis.net/w/cdev/ Sun, 10 Jul 2022 00:00:00 +1200 <p>This article assumes advanced knowledge of C and a basic understanding of the FreeBSD kernel and programming environment. It is also meant to serve as a template/reference and not a complete implementation.</p> <p><a href="https://git.sr.ht/~crm/random/tree/master/item/mydev_freebsd">Sample code can be found here</a>.</p> <p>Also mirrored on the <a href="https://wiki.freebsd.org/CDevModule">FreeBSD Wiki</a>.</p> <h2 id="table-of-contents">Table of contents</h2> <ul> <li><a href="#implementing-the-device">Implementing the device</a> <ul> <li><a href="#malloc-declaration">malloc declaration</a></li> <li><a href="#cdevsw-structure"><code>cdevsw</code> structure</a></li> <li><a href="#open-and-close">open() and close()</a></li> <li><a href="#read-and-write">read() and write()</a></li> <li><a href="#ioctl">ioctl()</a></li> </ul> </li> <li><a href="#creating-and-destroying-the-device">Creating and destroying the device</a></li> <li><a href="#module-declaration">Module declaration</a></li> <li><a href="#makefile">Makefile</a></li> <li><a href="#running-the-module">Running the module</a></li> <li><a href="#testing">Testing</a></li> </ul> <h2 id="implementing-the-device">Implementing the device</h2> <h3 id="malloc-declaration">malloc declaration</h3> <p>Kernel modules have their own malloc types, which are defined as follows:</p> <pre tabindex="0"><code>MALLOC_DECLARE(M_MYDEV); MALLOC_DEFINE(M_MYDEV, &#34;mydev&#34;, &#34;device description&#34;); </code></pre><p>Then, you can use malloc(9) and free(9) as:</p> <pre tabindex="0"><code>p = malloc(sizeof(foo), M_MYDEV, M_WAITOK | M_ZERO); free(p, M_MYDEV); </code></pre><h3 id="cdevsw-structure"><code>cdevsw</code> structure</h3> <p>The device&rsquo;s properties and methods are stored in a <code>cdevsw</code> (Character Device Switch) structure, defined in <code>sys/conf.h</code>. The fields we care about most of the time are the following:</p> <pre tabindex="0"><code>struct cdevsw { int d_version; u_int d_flags; const char *d_name; d_open_t *d_open; d_fdopen_t *d_fdopen; d_close_t *d_close; d_read_t *d_read; d_write_t *d_write; d_ioctl_t *d_ioctl; d_poll_t *d_poll; d_mmap_t *d_mmap; d_strategy_t *d_strategy; dumper_t *d_dump; d_kqfilter_t *d_kqfilter; d_purge_t *d_purge; d_mmap_single_t *d_mmap_single; ... }; </code></pre><p>All the <code>*_t</code> pointers are pointers to functions meant to be implemented by the driver. Not all functions have to be implemented however, but we usually do need to implement open(), close(), read(), write() and ioctl().</p> <p>Declare the functions using some handy typedefs:</p> <pre tabindex="0"><code>static d_open_t mydev_open; static d_close_t mydev_close; static d_read_t mydev_read; static d_write_t mydev_write; static d_ioctl_t mydev_ioctl; </code></pre><p>Declare the <code>cdevsw</code> structure:</p> <pre tabindex="0"><code>static struct cdevsw mydev_cdevsw = { .d_name = &#34;mydev&#34;, .d_version = D_VERSION, .d_flags = D_TRACKCLOSE, .d_open = mydev_open, .d_close = mydev_close, .d_read = mydev_read, .d_write = mydev_write, .d_ioctl = mydev_ioctl, }; </code></pre><p>The <code>D_TRACKCLOSE</code> flag tells the kernel to track when the device closes so that it can close normally in case something goes wrong.</p> <h3 id="open-and-close">open() and close()</h3> <p>Those two functions are mainly used for resource allocation/deallocation and environment preparation:</p> <pre tabindex="0"><code>static int mydev_open(struct cdev *dev, int flags, int devtype, struct thread *td) { int error = 0; /* do stuff */ return (error); } static int mydev_close(struct cdev *dev, int flags, int devtype, struct thread *td) { int error = 0; /* do stuff */ return (error); } </code></pre><h3 id="read-and-write">read() and write()</h3> <p>It&rsquo;s good practice to keep an internal buffer. Below is a very simplified example. The buffer in this example is allocated and deallocated on <a href="#module-declaration">module load and unload respectively</a>:</p> <pre tabindex="0"><code>#define BUFSIZE (1 &lt;&lt; 16) struct foo { char buf[BUFSIZE + 1]; size_t len; }; static struct foo *foo; </code></pre><p>Data to be received or sent back is stored in <code>uio</code> and the copy from user to kernel memory is done through uiomove(9), defined in <code>sys/uio.h</code>:</p> <pre tabindex="0"><code>static int mydev_read(struct cdev *dev, struct uio *uio, int ioflag) { size_t amnt; int v, error = 0; /* * Determine how many bytes we have to read. We&#39;ll either read the * remaining bytes (uio-&gt;uio_resid) or the number of bytes requested by * the caller. */ v = uio-&gt;uio_offset &gt;= foo-&gt;len + 1 ? 0 : foo-&gt;len + 1 - uio-&gt;uio_offset; amnt = MIN(uio-&gt;uio_resid, v); /* Move the bytes from foo-&gt;buf to uio. */ if ((error = uiomove(foo-&gt;buf, amnt, uio)) != 0) { /* error handling */ } /* do stuff */ return (error); } static int mydev_write(struct cdev *dev, struct uio *uio, int ioflag) { size_t amnt; int error = 0; /* Do not allow random access. */ if (uio-&gt;uio_offset != 0 &amp;&amp; (uio-&gt;uio_offset != foo-&gt;len)) return (EINVAL); /* We&#39;re not appending, reset length. */ else if (uio-&gt;uio_offset == 0) foo-&gt;len = 0; amnt = MIN(uio-&gt;uio_resid, (BUFSIZE - foo-&gt;len)); if ((error = uiomove(foo-&gt;buf + uio-&gt;uio_offset, amnt, uio)) != 0) { /* error handling */ } foo-&gt;len = uio-&gt;uio_offset; foo-&gt;buf[foo-&gt;len] = &#39;\0&#39;; /* do stuff */ return (error); } </code></pre><h3 id="ioctl">ioctl()</h3> <p>To create an ioctl, you give it a name and <code>#define</code> it using one of the following <code>_IO*</code> macros defined in <code>sys/ioccom.h</code>:</p> <ul> <li><code>_IO</code>: No parameters.</li> <li><code>_IOR</code>: Copy out parameters. Read from device.</li> <li><code>_IOW</code>: Copy in paramters. Write to device.</li> <li><code>_IOWR</code>: Copy parameters in and out. Write to device and read the modified data back.</li> </ul> <p>Each of those macros* takes 3 arguments:</p> <ul> <li>An arbitrary one-byte &ldquo;class&rdquo; identifier.</li> <li>A unique ID.</li> <li>The parameter type (can be anything), which is used to calculate the parameter&rsquo;s size. The macro expands the type to <code>sizeof(type)</code>.</li> </ul> <p>* <code>_IO</code> takes only the first 2 arguments (class and ID) since it doesn&rsquo;t use parameters.</p> <p>We can now define a few ioctls that take <code>foo_t</code> as a parameter. This is usually done in a separate header file so that programs can use the ioctls:</p> <pre tabindex="0"><code>#include &lt;sys/ioccom.h&gt; typedef struct { int x; int y; } foo_t; #define MYDEVIOC_READ _IOR(&#39;a&#39;, 1, foo_t) #define MYDEVIOC_WRITE _IOW(&#39;a&#39;, 2, foo_t) #define MYDEVIOC_RDWR _IOWR(&#39;a&#39;, 3, foo_t) </code></pre><p><code>mydev_ioctl()</code> is responsible for handling the ioctls we declared:</p> <pre tabindex="0"><code>static int mydev_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td) { foo_t *fp; int error = 0; switch (cmd) { case MYDEVIOC_READ: fp = (foo_t *)addr; /* do stuff */ break; case MYDEVIOC_WRITE: fp = (foo_t *)addr; /* do stuff */ break; case MYDEVIOC_RDWR: fp = (foo_t *)addr; /* do stuff */ break; default: error = ENOTTY; break; } return (error); } </code></pre><h2 id="creating-and-destroying-the-device">Creating and destroying the device</h2> <p>Character devices are given a <code>struct cdev</code> handle upon creation, which we usually store as a global variable:</p> <pre tabindex="0"><code>static struct cdev *mydev_cdev; </code></pre><p>Devices are created with the <code>make_dev()</code> function, which is defined as:</p> <pre tabindex="0"><code>struct cdev * make_dev(struct cdevsw *cdevsw, int unit, uid_t uid, gid_t gid, int perms, const char *fmt, ...); </code></pre><p><code>sys/conf.h</code> has the definitions of all available flags.</p> <p>Create the device:</p> <pre tabindex="0"><code>mydev_cdev = make_dev(&amp;mydev_cdevsw, 0, UID_ROOT, GID_WHEEL, 0666, &#34;mydev&#34;); </code></pre><p>When done, destroy the device:</p> <pre tabindex="0"><code>destroy_dev(mydev_cdev); </code></pre><h2 id="module-declaration">Module declaration</h2> <p>Necessary includes:</p> <pre tabindex="0"><code>#include &lt;sys/types.h&gt; #include &lt;sys/param.h&gt; #include &lt;sys/conf.h&gt; #include &lt;sys/systm.h&gt; #include &lt;sys/kernel.h&gt; #include &lt;sys/module.h&gt; #include &lt;sys/malloc.h&gt; #include &lt;sys/uio.h&gt; </code></pre><p>Implement the module&rsquo;s event handler. This function is called at module load and unload. Since we&rsquo;re dealing with a character device, it makes sense to create the device upon load and destroy it upon unload:</p> <pre tabindex="0"><code>static int mydev_modevent(module_t mod, int type, void *arg) { int error = 0; switch (type) { case MOD_LOAD: mydev_cdev = make_dev(&amp;mydev_cdevsw, 0, UID_ROOT, GID_WHEEL, 0666, &#34;mydev&#34;); foo = malloc(sizeof(foo_t), M_MYDEV, M_WAITOK | M_ZERO); foo-&gt;buf[0] = &#39;\0&#39;; foo-&gt;len = 0; break; case MOD_UNLOAD: /* FALLTHROUGH */ case MOD_SHUTDOWN: free(foo, M_MYDEV); destroy_dev(mydev_cdev); break; default: error = EOPNOTSUPP; break; } return (error); } </code></pre><p>Lastly, declare the module. The first argument is the module&rsquo;s name, the second one is a pointer to the event handler and the last one is any data we want to supply the event handler with, i.e., the <code>arg</code> argument in <code>mydev_modevent()</code>:</p> <pre tabindex="0"><code>DEV_MODULE(mydev, mydev_modevent, NULL); </code></pre><h2 id="makefile">Makefile</h2> <pre tabindex="0"><code>KMOD= mydev SRCS= mydev.c .include &lt;bsd.kmod.mk&gt; </code></pre><h2 id="running-the-module">Running the module</h2> <pre tabindex="0"><code>$ make # kldload ./mydev.ko ... # kldunload ./mydev.ko $ make clean cleandepend </code></pre><h2 id="testing">Testing</h2> <p>To test the module, load it, and create a simple program that opens the device, and makes a few calls to ioctl(2), read(2) and write(2).</p> RSS: Advantages and usage https://margiolis.net/w/rss/ Fri, 27 May 2022 00:00:00 +1200 <p>RSS (Really Simple Syndication) is a standardized protocol for creating and sharing web feeds. An RSS feed is an XML file consisting of entries, each of them usually corresponding to an article. Programs called &ldquo;RSS aggregators&rdquo;, parse these files and provide a nice display where you can nagivate through each entry as if it were a news feed. This is a very convenient way to &ldquo;follow&rdquo; websites because all you have to do is grab their RSS URL, add it to your aggregator, and read the articles once it parses the feed.</p> <p><img src="https://margiolis.net/rss.png" alt="rss"></p> <p>On the website owner&rsquo;s end, a new entry needs to be added whenever a new article is published. The entry usually includes a short summary of the article, or as is the case for my website, the whole article, so that you can read it all through your RSS reader.</p> <h2 id="why-bother">Why bother</h2> <p>RSS is an open protocol, it&rsquo;s decentralized, and very simple both in terms of parsing and maintaining. Because RSS is such a simple protocol, it can adapt to everyone&rsquo;s workflow. There are RSS readers for Android, Windows, UNIX, web browsers, email clients, etc.</p> <p>Apart from the technical advantages RSS provides, it&rsquo;s also a great way to have a personal, uncensored feed, following only the websites you want &mdash; even YouTube channels &mdash; without ads and additional noise and distractions.</p> <p>I personally use <a href="https://codemadness.org/sfeed-simple-feed-parser.html">sfeed</a> as an RSS reader, because I like the extensibility it offers. I&rsquo;ve written <a href="https://ftp.margiolis.net/patch/sfeed_curses_bookmarks.diff">a small patch for sfeed_curses(1)</a>, so that you can bookmark feed entry URLs to a predefined file. I use this mostly to queue videos and podcasts and then stream them through <a href="https://mpv.io/">mpv</a> using <a href="https://git.sr.ht/~crm/scripts/tree/master/item/vdq">a very simple script I created</a>, which, all it does is read the &ldquo;queue&rdquo; file and pipe the URLs to mpv.</p> <h2 id="how-to-create-and-maintain-an-rss-feed">How to create and maintain an RSS feed</h2> <p>Below is a very basic RSS feed. Inside the <code>&lt;channel&gt;</code> tags is the whole feed, and inside <code>&lt;item&gt;</code> is each individual entry. The rest of the tags are pretty self-explanatory, if not, feel free to <a href="https://www.rssboard.org/rss-specification">read the specification</a>. The article/summary is placed inside <code>&lt;description&gt;</code> &mdash; that can be in plain text or <a href="https://www.rssboard.org/rss-encoding-examples">encoded HTML</a>:</p> <pre tabindex="0"><code>&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34; ?&gt; &lt;rss version=&#34;2.0&#34;&gt; &lt;channel&gt; &lt;title&gt;Example Org&lt;/title&gt; &lt;description&gt;Example Org&#39;s RSS feed&lt;/description&gt; &lt;link&gt;http://www.example.org/rss.xml&lt;/link&gt; &lt;item&gt; &lt;title&gt;Example entry&lt;/title&gt; &lt;link&gt;http://www.example.org/blog/post.html&lt;/link&gt; &lt;pubDate&gt;Sun, 06 Sep 2009 16:20:00 +0000&lt;/pubDate&gt; &lt;description&gt; Here is some text containing an interesting description. &lt;/description&gt; &lt;/item&gt; &lt;/channel&gt; &lt;/rss&gt; </code></pre><p>To create a new entry, you can either handwrite it in the XML file yourself, make a script or small program to do it automatically, or use some existing tool.</p> <p>To share the feed, simply add the URL to your website and tell people to subscribe to it. For example, my website&rsquo;s RSS feed can be obtained from <a href="https://margiolis.net/w/rss.xml">https://margiolis.net/w/rss.xml</a>.</p> <h2 id="rss-for-some-popular-platforms">RSS for some popular platforms</h2> <h3 id="youtube">YouTube</h3> <p><del>Go to the channel&rsquo;s home page, right-click and click on &ldquo;Page source&rdquo; and search for <code>channelId&quot; content</code>. Copy the hash inside <code>content=&quot;&quot;</code> and append it to the following URL (replace <code>FILLME</code>):</del></p> <pre tabindex="0"><code>https://www.youtube.com/feeds/videos.xml?channel_id=FILLME </code></pre><p>You can easily fetch YouTube RSS URLs <a href="https://ytrss.pesky.moe/">here</a>.</p> <h3 id="github">GitHub</h3> <p>You can get an RSS feed for each new commit that happens in a given branch. Replace <code>username</code>, <code>repo</code> and <code>branch</code> with the correct values:</p> <pre tabindex="0"><code>https://github.com/username/repo/commits/branch.atom </code></pre><h3 id="social-media">Social media</h3> <p><a href="https://margiolis.net/w/socialmedia">Quit social media</a>.</p> <h3 id="news">News</h3> <p><a href="https://margiolis.net/w/news">Avoid the news</a>.</p> Share ZFS datasets with NFS https://margiolis.net/w/zfsnfs/ Sat, 21 May 2022 00:00:00 +1200 <p>Thanks to Mark Johnston &lt;markj@FreeBSD.org&gt; for recommending this configuration. The article is also mirrored on the <a href="https://wiki.freebsd.org/ZFS/ShareNFS">FreeBSD Wiki</a>.</p> <h2 id="host">Host</h2> <p>Add the following lines to <code>/etc/rc.conf</code>:</p> <pre tabindex="0"><code>nfs_server_enable=&#34;YES&#34; mountd_enable=&#34;YES&#34; mountd_flags=&#34;-n&#34; rpc_lockd_enable=&#34;YES&#34; rpc_statd_enable=&#34;YES&#34; rpcbind_enable=&#34;YES&#34; </code></pre><p>For some reason the NFS server needs to be restarted the first time after boot. A simple way to do this automatically is:</p> <pre tabindex="0"><code># echo &#34;service nfsd restart&#34; &gt;&gt; /etc/rc.local </code></pre><p>Set the <code>sharenfs</code> property to the dataset you want to share. Replace the IPs and <code>pool/dataset*</code> with your desired values. ZFS properties are documented in <a href="https://man.freebsd.org/zfsprops/7">zfsprops(8)</a>.</p> <p>We&rsquo;re going to share 2 datasets, one with read-write and one with read-only access:</p> <pre tabindex="0"><code># chmod -R 777 /pool/dataset_rw # zfs set sharenfs=&#34;-alldirs,-network=192.168.1.0/24&#34; pool/dataset_rw # zfs set sharenfs=&#34;-ro,-alldirs,-network=192.168.1.0/24&#34; pool/dataset_ro </code></pre><p>Start the NFS server:</p> <pre tabindex="0"><code># service nfsd start # service mountd reload </code></pre><h2 id="guest">Guest</h2> <p>Acquire the host&rsquo;s IP address using ifconfig(8) on it first. We&rsquo;ll assume it&rsquo;s 192.168.1.5.</p> <p>Mount the filesystems. <code>/pool/dataset*</code> corresponds to the actual mount point of the dataset in the host:</p> <pre tabindex="0"><code># mkdir -p /mnt/dataset_rw /mnt/dataset_ro # mount -t nfs -o rw 192.168.1.5:/pool/dataset_rw /mnt/dataset_rw # mount -t nfs -o ro 192.168.1.5:/pool/dataset_ro /mnt/dataset_ro </code></pre><p>When done, unmount:</p> <pre tabindex="0"><code># umount /mnt/dataset_rw /mnt/dataset_ro </code></pre><p>In case you want the filesystems to be mounted on boot, add the following lines to <code>/etc/fstab</code>:</p> <pre tabindex="0"><code>192.168.1.5:/dataset_rw /mnt/dataset_rw nfs rw 0 0 192.168.1.5:/dataset_ro /mnt/dataset_ro nfs ro 0 0 </code></pre><h2 id="further-reading">Further reading</h2> <ul> <li><a href="https://docs.freebsd.org/en/books/handbook/network-servers/">FreeBSD Handbook: Network Servers</a></li> </ul> Process queuing using lock files https://margiolis.net/w/procqueue/ Wed, 18 May 2022 00:00:00 +1200 <p>Locking will be done using the <a href="https://man.openbsd.org/fcntl">fcntl(2) system call</a>. I&rsquo;m aware of lockf(3) and flock(2), but both of them normally use fcntl(2) under the hood, and they are not as portable.</p> <p>For a real use-case, I&rsquo;ve written a <a href="https://margiolis.net/w/nfy">notification program</a> which uses the same mechanism, so that notifications can be queued without having to run a daemon, such as D-Bus, in the background.</p> <p>First create the lock file with write permissions. <code>O_CREAT</code> is used to create the file in case it doesn&rsquo;t exist already:</p> <pre tabindex="0"><code>#include &lt;err.h&gt; #include &lt;fcntl.h&gt; ... char *lockfile = &#34;/tmp/foo.lock&#34;; int fd; if ((fd = open(lockfile, O_CREAT | O_WRONLY, 0600)) &lt; 0) err(1, &#34;open(%s)&#34;, lockfile); </code></pre><p>Locking commands operate on the <code>flock</code> structure. Before a call to fcntl(2) is made, we need to write the following fields:</p> <pre tabindex="0"><code>struct flock { off_t l_start; /* starting offset */ off_t l_len; /* len = 0 means until end of file */ short l_type; /* lock type: read/write, etc. */ short l_whence; /* type of l_start */ ... }; </code></pre><p>The starting offset, <code>l_len</code>, can be anything, but 0 is what makes the most sense. We&rsquo;ll set <code>l_len</code> to 0 as well, since we want each process to lock the entire file. The lock type, <code>l_type</code>, needs to be an exclusive lock (<code>F_WRLCK</code>), that is, a lock that prevents any other process from setting a lock on that area before it&rsquo;s released. <code>l_whence</code> will be set to <code>SEET_SET</code> to indicate that the relative offset <code>l_start</code> will be measured from the beginning of the file:</p> <pre tabindex="0"><code>struct flock fl; fl.l_len = 0; fl.l_start = 0; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; </code></pre><p>The <code>F_SETLKW</code> command will make the calling process wait until the lock request can be satisfied. There&rsquo;s also <code>F_SETLK</code>, but it returns immidiately if the lock is already acquired, which is not very useful for queuing processes:</p> <pre tabindex="0"><code>if (fcntl(fd, F_SETLKW, &amp;fl) &lt; 0) err(1, &#34;fcntl(F_SETLKW)&#34;); </code></pre><p>When we get past the call to fcntl(2), it means that we have acquired the lock until a call to close(2) is made. Here is where we&rsquo;ll put the part of the code we want to queue, in this case a simple <code>printf</code> followed by a 3-second sleep to make sure queuing really works:</p> <pre tabindex="0"><code>printf(&#34;hello from %d\n&#34;, getpid()); sleep(3); </code></pre><p>When done, release the lock:</p> <pre tabindex="0"><code>close(fd); </code></pre><p>To test the code, open two terminals and run the program on both of them. You&rsquo;ll see the process that was run last will not execute the code after fcntl(2) until the first one has finished.</p> FreeBSD VNET Jails https://margiolis.net/w/vnetjail/ Sat, 30 Apr 2022 00:00:00 +1200 <p>This article is also mirrored on the <a href="https://wiki.freebsd.org/Jails/VNET">FreeBSD Wiki</a>.</p> <h2 id="configuration">Configuration</h2> <p>The VNET is set up using an <a href="https://man.freebsd.org/if_epair/4">if_epair(4)</a> interface bridged with the actual network interface, in my case <code>re0</code>. Just like if_tap(4) interfaces, epairs can be used by one jail at a time, so if you need to run more than one jail at the same time, you have to make more epairs.</p> <p>If you&rsquo;re using tap interfaces for bhyve VMs, you can just <code>addm</code> them to the bridge.</p> <p>Add the following lines to <code>/etc/rc.conf</code>:</p> <pre tabindex="0"><code>if_bridge_load=&#34;YES&#34; if_epair_load=&#34;YES&#34; cloned_interfaces=&#34;bridge0&#34; ifconfig_bridge0=&#34;addm re0 up&#34; </code></pre><p>Apply changes:</p> <pre tabindex="0"><code># /etc/netstart </code></pre><p>The jail needs to inherit <code>/dev/bpf*</code> from the host in order for networking to work at all. Make a new <code>/etc/devfs.rules</code> ruleset:</p> <pre tabindex="0"><code>[devfsrules_jails=5] add include $devfsrules_hide_all add include $devfsrules_unhide_basic add include $devfsrules_unhide_login add path &#39;bpf*&#39; unhide </code></pre><p>Restart devfs(8):</p> <pre tabindex="0"><code># service devfs restart </code></pre><p>In <code>/etc/jail.conf</code>, we&rsquo;ll name the jail <code>foo</code> and give it the other end of the epair we&rsquo;ll create as its network interface. Its IP address will be acquired using DHCP. The reason I&rsquo;m manually calling dhclient(8) is because adding <code>ifconfig_epair0b=&quot;DHCP&quot;</code> in the jail&rsquo;s <code>/etc/rc.conf</code> doesn&rsquo;t work. Options are detailed in <a href="https://man.freebsd.org/jail.conf/5">jail.conf(5)</a>:</p> <pre tabindex="0"><code>path = &#34;/usr/local/jail/$name&#34;; host.hostname=&#34;$name&#34;; exec.clean; exec.start = &#34;/bin/sh /etc/rc&#34;; exec.stop = &#34;/bin/sh /etc/rc.shutdown&#34;; allow.mount; allow.raw_sockets = 1; mount.devfs; devfs_ruleset=&#34;5&#34;; vnet; sysvmsg=new; sysvsem=new; sysvshm=new; foo { vnet.interface = &#34;epair0b&#34;; exec.start += &#34;dhclient epair0b&#34;; } </code></pre><h2 id="installation">Installation</h2> <pre tabindex="0"><code># mkdir -p /usr/local/jail/foo # bsdinstall jail /usr/local/jail/foo ... # ifconfig epair0 create # ifconfig bridge0 addm epair0a # ifconfig epair0a up # service jail onestart foo </code></pre><p>Test to see if the jail has networking:</p> <pre tabindex="0"><code># jexec foo ping google.com </code></pre><p>On jail shutdown, destroy the epair:</p> <pre tabindex="0"><code># service jail onestop foo # ifconfig epair0a destroy </code></pre><h2 id="delete-jail">Delete jail</h2> <p>Deleting jails isn&rsquo;t as straight forward, so I&rsquo;m leaving this here as well:</p> <pre tabindex="0"><code># service jail onestop foo # chflags -R noschg /usr/local/jail/foo # rm -rf /usr/local/jail/foo </code></pre><h2 id="further-reading">Further reading</h2> <ul> <li><a href="https://freebsdfoundation.org/freebsd-project/resources/introduction-to-freebsd-jails/">An Introduction to FreeBSD Jails</a></li> <li><a href="https://docs.freebsd.org/en/books/handbook/jails/">FreeBSD Handbook: Jails</a></li> <li><a href="https://klarasystems.com/articles/virtualize-your-network-on-freebsd-with-vnet/">Virtualize your network on FreeBSD with VNET</a></li> </ul> A minimal and daemonless notification program for X https://margiolis.net/w/nfy/ Sat, 02 Apr 2022 00:00:00 +1200 <p><a href="https://git.sr.ht/~crm/nfy">Source code</a> | <a href="https://ftp.margiolis.net/nfy/nfy-0.2.tar.gz">Download 0.2</a> | <a href="https://margiolis.net/files/nfy.1.html">Man page</a></p> <h2 id="features">Features</h2> <ul> <li>Very lightweight. ~250 lines of C.</li> <li>Probably no dependencies, apart from the pre-installed X11 ones. <code>Xlib</code>, <code>libXft</code> and <code>libXrandr</code>.</li> <li>Multiple notifications are <a href="https://margiolis.net/w/procqueue">queued using a lock file</a>. This avoids the trouble of having a daemon (e.g D-Bus) constantly running in the background.</li> <li>Non-blocking behavior, so that it can be used inside scripts easily.</li> <li>Configuration is done by editing <code>config.h</code> and recompiling the source code, in a similar fashion to <a href="https://suckless.org/">suckless utilities</a>. No knowledge of C is required to edit the file. Compilation takes around 1 second.</li> <li>Clicking on the notification window will make it disappear.</li> </ul> <p>nfy works by piping text into it. Text cannot be passed as argument:</p> <pre tabindex="0"><code>$ echo &#39;hello world&#39; | nfy $ nfy &lt; foo.txt </code></pre><p>If <code>stdin</code> is empty, it exits with an error:</p> <pre tabindex="0"><code>$ nfy nfy: stdin is empty </code></pre><p><img src="https://margiolis.net/files/nfy.webp" alt="nfy"></p> <h2 id="install">Install</h2> <p>First make sure all settings in <code>config.mk</code> are set according to your system&rsquo;s configuration, then:</p> <pre tabindex="0"><code># make install clean </code></pre><h2 id="notes">Notes</h2> <p>If you want to use nfy(1) with cron(8), make sure to export the X Display variable inside the script running nfy(1):</p> <pre tabindex="0"><code>export DISPLAY=&#34;:0.0&#34; </code></pre><p>Alternatively, export the variable in the crontab file directly.</p> <p>Send feedback and bugs to <code>&lt;christos@margiolis.net&gt;</code>.</p> Why reinvent the wheel https://margiolis.net/w/reinvent_wheel/ Fri, 25 Mar 2022 00:00:00 +1200 <p>I&rsquo;ve got a few reasons for &ldquo;reinventing the wheel&rdquo;, some personal and some practical:</p> <ul> <li>I&rsquo;m curious and want to know how things really work.</li> <li>I like DIY from scratch projects.</li> <li>Bottom-up learning is valuable.</li> <li>There are things that are fundamentally flawed and need to be done again from scratch.</li> <li>Existing tools don&rsquo;t always match the criteria for your specific task and/or introduce too much complexity.</li> <li>Because there&rsquo;s already a way to do something, it doesn&rsquo;t mean it&rsquo;s the right way to do it.</li> </ul> <p>Some of the greatest innovations have in fact been a reinvention of the wheel, because what already existed at the time wasn&rsquo;t good enough. Imagine someone telling James Watt he shouldn&rsquo;t waste his time reinventing the wheel. Improving what already exists means we have stable foundations and better tools to work with in the future. The wheel itself has been reinvented multiple times, hence why we don&rsquo;t use Bronze Age wheels anymore.</p> <p><img src="https://margiolis.net/files/wheel.webp" alt="wheel"></p> <p>A good example in the software world of why sometimes reinventing the wheel is a good idea is UNIX vs Windows. UNIX was written from scratch to replace Multics which had serious flaws, so they ended up with a new and clean system, built right from the bottom-up. They reinvented the wheel and made huge innovations in the process. Windows on the other hand, is built on top of MS-DOS which even at the time was a mess, so what Microsoft is left with is an OS which up to this day is overly complicated, because of fundamental design flaws that cannot be fixed unless they do away with the whole thing and start from scratch. Instead, they are coming up with half-assed solutions and add complexity to the system with each update.</p> <p>Then there&rsquo;s quality and efficiency. Writing your own tools means they can be tailored to perform a specific task perfectly &mdash; something a big generalized framework or piece of software can&rsquo;t do in many cases. We tend to ignore the improvements better software can bring in the long term and instead spend time fighting with not-so-great tools and also rely on the power of modern hardware to cope for our mediocre solutions (see modern web tools or the web in general).</p> <p>So, am I proposing that we&rsquo;d be better off writing machine code and building our own programs for everything? Absolutely not. What I AM saying though, is that instead of being allergic to reinventing the wheel, we can start to see the merits in it, without overdoing it. This approach is benefecial to our personal improvement as engineers, which means an improvement in software as a whole. Just as you learn any other skill by mastering and understanding the basics, programming has to be understood from the bottom-up, not the opposite way. There&rsquo;s no point in knowing all kinds of fancy frameworks if all you see are black boxes, or cannot even implement a linked list if asked to. After all, it&rsquo;s a fact that software is getting worse.</p> <p>And to save myself from sounding ignorant, <strong>there are cases where reinventing the wheel is a problem</strong>, namely:</p> <ul> <li>Companies rolling their own proprietary solutions for everything, and ending up creating protocols and programs, which are arguably worse, expensive, harder to maintain, <em>and</em> incompatible with existing software.</li> <li>People who like to reinvent the wheel for the sake of just having &ldquo;their own&rdquo; version of something, even when there actually <em>is</em> good software out there that covers their needs just as well.</li> </ul> <p>Knowing how things really work will enable you to trace bugs easier, write/design more performant software, and overall know what the hell you&rsquo;re doing. And that can only be achieved by, sometimes, reinventing the wheel. Nothing can teach you what getting your hands dirty will.</p> <p><a href="https://medium.com/codex/dont-reinvent-the-wheel-and-other-web-developer-cop-outs-ed9dc4d6c9e3">A related article I found funny.</a></p> Git on 9front https://margiolis.net/w/git9/ Sun, 13 Mar 2022 00:00:00 +1200 <p>This article is also mirrored on the <a href="http://wiki.9front.org/git9">9front Wiki</a>.</p> <h2 id="resources">Resources</h2> <ul> <li><a href="http://man.9front.org/1/git">9front git man page</a></li> <li><a href="https://orib.dev/git9.html">Ori&rsquo;s article on Git9</a></li> </ul> <h2 id="user-configuration">User configuration</h2> <pre tabindex="0"><code>; mkdir $home/lib/git ; cat &gt; $home/lib/git/config [user] name=Your Name email=me@example.org ^D </code></pre><h2 id="examples">Examples</h2> <p>Create, commit and push a repo:</p> <pre tabindex="0"><code>; cd repo ; git/init # add a remote in .git/config... ; git/add . ; git/commit -m &#39;commitmsg&#39; foo.c heads/front: 817a3f121083091291c45f1ddfcd1b042343efab ; git/push </code></pre><p>Clone and push changes to a repo:</p> <pre tabindex="0"><code>; git/clone git://git.example.org/repo ; cd repo # make changes... ; git/commit foo.c ; git/push </code></pre><p>Make a patch:</p> <pre tabindex="0"><code># make changes... ; git/commit -m &#39;commitmsg&#39; foo.c heads/front: 817a3f121083091291c45f1ddfcd1b042343efab ; git/export &gt; patch.diff </code></pre><p>Apply a patch:</p> <pre tabindex="0"><code>; git/import &lt; patch.diff applying commitmsg </code></pre><p>See which files have changed:</p> <pre tabindex="0"><code>; git/diff -s M foo.c M bar.c </code></pre><h2 id="shithub-usage">Shithub usage</h2> <p>First ask Ori (ori AT eigenstate DOT org) for a user.</p> <p>Create and push a repository:</p> <pre tabindex="0"><code>; rcpu -u $user -h shithub.us -c \ newrepo -d &#39;description&#39; -c &#39;me@example.org&#39; reponame ; git/push -u hjgit://shithub.us/$user/reponame </code></pre><p>Repositories live under <code>/usr/git/$user</code>. Each repo contains editable files in <code>/usr/git/$user/repo/.git</code>:</p> <dl> <dt><code>webpublish</code></dt> <dd>If this file exists, then the repository is published in the public web list of repositories.</dd> <dt><code>desc, description</code></dt> <dd>The short description of the repository. It shows up in the repo list.</dd> <dt><code>contact</code></dt> <dd>Contact information for submitting patches. Shows up on the repository info page.</dd> </dl> 9front CPU server setup https://margiolis.net/w/9front_cpu/ Mon, 07 Mar 2022 00:00:00 +1200 <p>The contents of <code>/net/ndb</code> will be useful throughout the whole process; have them visible in another window:</p> <pre tabindex="0"><code>; ip/ipconfig ; cat /net/ndb ip=192.168.1.152 ipmask=255.255.255.0 ipgw=192.168.1.3 sys=cirno dns=192.168.1.1 dns=192.168.1.1 </code></pre><h2 id="boot-settings">Boot settings</h2> <p>The <code>9fat</code> partition contains the kernel, bootloader and boot settings. Add the following lines to <code>plan9.ini</code>. Make sure the values match your own configuration:</p> <pre tabindex="0"><code>; 9fs 9fat ; cd /n/9fat ; cat &gt;&gt; plan9.ini user=glenda cpu=192.168.1.152 auth=192.168.1.152 authdom=someauth service=cpu ^D </code></pre><p><code>user</code> is kind of like the root user on UNIX systems, <code>cpu</code> and <code>auth</code> define the IP addresses for the CPU and auth server respectively, <code>authdom</code> is the domain name of our the server, and <code>service=cpu</code> starts the CPU listener at boot time.</p> <p>9front needs to boot hands-free, so the <code>bootargs</code> prompt has to be skipped. To do that, change the <code>bootargs</code> variable (its values might be different) in <code>plan9.ini</code>:</p> <pre tabindex="0"><code>bootargs=local!/dev/sdE0/fs # change to... noobootprompt=local!/dev/sdE0/fs -m 291 -A -a tcp!*!564 </code></pre><h2 id="auth-server">Auth server</h2> <p>Set up the auth server hostowner&rsquo;s name and password; <code>authid</code> and <code>authdom</code> have to match the values in <code>plan9.ini</code>:</p> <pre tabindex="0"><code>; auth/wrkey bad nvram des key bad authentication id bad authentication domain authid: glenda authdom: someauth secstore key: password: &lt;your_pass&gt; </code></pre><p>Add <code>glenda</code>&rsquo;s password. This <em>has</em> to be the same as the one you typed in wrkey(8). Always run keyfs(4) before modifying keys:</p> <pre tabindex="0"><code>; auth/keyfs ; auth/changeuser glenda Password: &lt;your_pass&gt; Confirm password: &lt;your_pass&gt; assign new Inferno/POP secret? [y/n]: y make it the same as Plan 9 password? [y/n]: y Expiration date (YYYYMMDD or never)[never]: Post id: User&#39;s full name: Department #: User&#39;s email address: user glenda installed for Plan 9 ; auth/enable glenda </code></pre><h2 id="network-database">Network database</h2> <p>Add the following lines to <code>/lib/ndb/local</code> (pay close attention to detail, especially <code>ip=</code>):</p> <pre tabindex="0"><code>; cat &gt;&gt; /lib/ndb/local authdom=someauth auth=192.168.1.152 ipnet=iphome ip=192.168.1.0 ipmask=255.255.255.0 ipgw=192.168.1.3 auth=192.168.1.152 authdom=someauth fs=192.168.1.152 cpu=192.168.1.152 dns=192.168.1.1 ^D </code></pre><p><code>ipnet</code> can be anything.</p> <p>Sync the changes and reboot the system:</p> <pre tabindex="0"><code>; echo sync &gt;&gt; /srv/hjfs.cmd ; fshalt -r </code></pre><h2 id="on-reboot">On reboot</h2> <p>Enable networking:</p> <pre tabindex="0"><code>; bind -a &#39;#l0&#39; /net ; ip/ipconfig ; ip/ipconfig ether /net/ether0 </code></pre><p>Drawterm as <code>glenda</code>:</p> <pre tabindex="0"><code>$ drawterm -u glenda cpu[cpu]: 192.168.1.152 auth[192.168.1.152]: glenda@someauth dp9ik password: &lt;your_pass&gt; </code></pre><p>Start a rio(1) instance:</p> <pre tabindex="0"><code>cpu% rio -s -i riostart </code></pre><p><code>/cfg/$sysname/cpustart</code> contains commands that run on boot. You can put whatever you want in it &mdash; I&rsquo;m going to just add the same commands I ran after we rebooted to make my life easier.</p> <pre tabindex="0"><code>; mkdir /cfg/$sysname ; cat &gt; /cfg/$sysname/cpustart bind -a &#39;#l0&#39; /net ip/ipconfig ip/ipconfig ether /net/ether0 cat /net/ndb ^D </code></pre><h2 id="new-user">New user</h2> <p>Add the new user to the file server, add it to some standard groups and assign it a password:</p> <pre tabindex="0"><code>; echo newuser christos &gt;&gt; /srv/hjfs.cmd ; echo newuser sys +christos &gt;&gt; /srv/hjfs.cmd ; echo newuser adm +christos &gt;&gt; /srv/hjfs.cmd ; echo newuser upas +christos &gt;&gt; /srv/hjfs.cmd ; echo newuser cron +christos &gt;&gt; /srv/hjfs.cmd ; auth/keyfs ; auth/changeuser christos ... ; auth/enable christos </code></pre><p>Let the new user &ldquo;speak for&rdquo; for other users by adding the following lines to <code>/lib/ndb/auth</code>:</p> <pre tabindex="0"><code>; cat &gt;&gt; /lib/ndb/auth hostid=christos uid=!sys uid=!adm uid=* ^D </code></pre><p>Reboot for the changes to take effect:</p> <pre tabindex="0"><code>; fshalt -r </code></pre><p>Drawterm as <code>christos</code> and run newuser(8) to set up his home directory:</p> <pre tabindex="0"><code>$ drawterm -u christos ... ; /sys/lib/newuser </code></pre> Acme editor notes https://margiolis.net/w/acme_notes/ Sun, 06 Mar 2022 00:00:00 +1200 <p><img src="https://margiolis.net/files/acme.webp" alt="acme"></p> <h2 id="resources">Resources</h2> <ul> <li><a href="https://acme.cat-v.org/">The Acme User Interface for Programmers</a></li> <li><a href="https://www.youtube.com/watch?v=dP1xVpMPn8M">A Tour of the Acme Editor</a> - Watch this if you&rsquo;re completely new.</li> <li><a href="https://www.youtube.com/watch?v=dopu3ZtdCsg">Plan9 Acme Intro - Part 1</a></li> <li><a href="https://benghancock.github.io/blog/2022/tao-of-acme.html">The Tao of Acme</a></li> <li><a href="https://mostlymaths.net/2013/03/extensibility-programming-acme-text-editor.html/">Extensibility in the Acme text editor</a></li> </ul> <h2 id="mouse">Mouse</h2> <p>Acme needs a 3-button mouse to work properly. All buttons can be used to select text, but their function is different upon release. The left button is used for just selecting text. The middle button is used for executing text. The right button is for searching text and loading files or directories. Text doesn&rsquo;t have to first be selected when using button 2 and 3 (middle and right) if the selection is only one word.</p> <p>Acme also makes use of mouse chording (e.g pressing more than one button).</p> <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td>Button 1-2</td> <td>Cut.</td> </tr> <tr> <td>Button 1-3</td> <td>Paste</td> </tr> <tr> <td>Select text + Button 2-1</td> <td>Execute with selection as argument.</td> </tr> </tbody> </table> <h2 id="move-around">Move around</h2> <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td><code>:2</code></td> <td>Go to 2nd line.</td> </tr> <tr> <td><code>:0</code></td> <td>Go to beginning of file.</td> </tr> <tr> <td><code>:$</code></td> <td>Go to end of file.</td> </tr> <tr> <td><code>CTRL-A</code></td> <td>Go to beginning of line.</td> </tr> <tr> <td><code>CTRL-E</code></td> <td>Go to end of line.</td> </tr> <tr> <td><code>CTRL-F</code></td> <td>Filepath autocompletion.</td> </tr> <tr> <td><code>Edit =</code></td> <td>Find current line number.</td> </tr> </tbody> </table> <h2 id="search-and-select">Search and select</h2> <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td><code>:,</code></td> <td>Select all lines.</td> </tr> <tr> <td><code>Edit ,</code></td> <td>Select all lines.</td> </tr> <tr> <td><code>:1,5</code></td> <td>Select lines 1 to 5.</td> </tr> <tr> <td><code>Edit 1,5</code></td> <td>Select lines 1 to 5.</td> </tr> <tr> <td><code>:/regexp/</code></td> <td>Select lines matching <code>regexp</code>.</td> </tr> <tr> <td><code>:/regexp1/,/regexp2/</code></td> <td>Select lines between <code>regexp1</code> and <code>regexp2</code>.</td> </tr> <tr> <td><code>Edit + /foo</code></td> <td>Forward serach.</td> </tr> <tr> <td><code>:/foo</code></td> <td>Forward search.</td> </tr> <tr> <td><code>:foo</code></td> <td>Forward search.</td> </tr> <tr> <td>Right click kon word.</td> <td>Forward search.</td> </tr> <tr> <td><code>Edit -/foo</code></td> <td>Backward search.</td> </tr> <tr> <td><code>:-/foo</code></td> <td>Backward search.</td> </tr> <tr> <td><code>-/foo</code></td> <td>Backward search.</td> </tr> </tbody> </table> <h2 id="edit-text">Edit text</h2> <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td><code>CTRL-U</code></td> <td>Delete from cursor to beginning of line.</td> </tr> <tr> <td><code>CTRL-W</code></td> <td>Delete before cursor.</td> </tr> <tr> <td><code>CTRL-H</code></td> <td>Delete character before cursor.</td> </tr> <tr> <td><code>Edit , d</code></td> <td>Clear window.</td> </tr> <tr> <td><code>Edit , s/foo/bar/g</code></td> <td>Global replace.</td> </tr> <tr> <td><code>Edit , | sed 's/foo/bar/g'</code></td> <td>Global replace.</td> </tr> <tr> <td><code>Edit s/foo/bar/g</code></td> <td>Replace in selection.</td> </tr> <tr> <td><code>Edit 2 d</code></td> <td>Delete 2nd line.</td> </tr> <tr> <td><code>Edit 2 c/foo</code></td> <td>Change 2nd line.</td> </tr> <tr> <td><code>Edit 2 a/foo</code></td> <td>Append after 2nd line.</td> </tr> <tr> <td><code>Edit 2 i/foo</code></td> <td>Prepend before 2nd line.</td> </tr> </tbody> </table> <h2 id="work-with-external-commands">Work with external commands</h2> <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td><code>cmd</code></td> <td>Run <code>cmd</code> with the file as argument.</td> </tr> <tr> <td><code>|cmd</code></td> <td>Pipe selection through <code>cmd</code> and also apply changes.</td> </tr> <tr> <td><code>&gt;cmd</code></td> <td>Send selection to <code>cmd</code> and see result in a temporary window.</td> </tr> <tr> <td><code>&lt;cmd</code></td> <td>Paste output of <code>cmd</code> in current window (no selection required).</td> </tr> </tbody> </table> <p>Any command can be used, these are just a few examples.</p> <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td><code>Edit , &lt; echo foo</code></td> <td>Replace window body with some text (works with any command).</td> </tr> <tr> <td><code>echo foo | 9p write acme/$winid/body</code></td> <td>Append to end of current window body.</td> </tr> <tr> <td><code>Edit , &gt; wc -l</code></td> <td>Count lines.</td> </tr> <tr> <td><code>Edit , | sort</code></td> <td>Sort lines.</td> </tr> <tr> <td><code>Edit ,x/regexp/ &lt; date</code></td> <td>Replace <code>regexp</code> with the output of date(1).</td> </tr> <tr> <td>Select text + <code>| sed '' &gt; foo.txt</code></td> <td>Cut to <code>foo.txt</code>.</td> </tr> <tr> <td>Select text + <code>&gt; sed '' &gt; foo.txt</code></td> <td>Copy to <code>foo.txt</code>.</td> </tr> </tbody> </table> <h2 id="files">Files</h2> <p>As mentioned earlier, files are opened using the right mouse click.</p> <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td><code>foo.c</code></td> <td>Open file.</td> </tr> <tr> <td><code>foo.c:3</code></td> <td>Open file on line 3.</td> </tr> <tr> <td><code>foo.c:3:9</code></td> <td>Open file on line 3 column 9.</td> </tr> <tr> <td><code>foo.c:/^func</code></td> <td>Open file on the line starting with <code>func</code>.</td> </tr> <tr> <td><code>foo.c:/bar/,/baz/</code></td> <td>Open file with a selection from <code>bar</code> to <code>baz</code>.</td> </tr> </tbody> </table> <h2 id="useful-acme-commands">Useful Acme commands</h2> <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td><code>win</code></td> <td>Spawn shell as a window.</td> </tr> <tr> <td><code>web URL</code></td> <td>Open <code>URL</code> in browser.</td> </tr> <tr> <td><code>Dump</code></td> <td>Save current state.</td> </tr> <tr> <td><code>Load</code></td> <td>Load dump.</td> </tr> <tr> <td><code>Tab 8</code></td> <td>Set tab width to 8.</td> </tr> </tbody> </table> <h2 id="variables">Variables</h2> <table> <thead> <tr> <th></th> <th></th> </tr> </thead> <tbody> <tr> <td><code>$%</code></td> <td>Current file name.</td> </tr> <tr> <td><code>$samfile</code></td> <td>Current file name.</td> </tr> <tr> <td><code>$winid</code></td> <td>Current window.</td> </tr> </tbody> </table> FreeBSD sound mixer improvements https://margiolis.net/w/mixer_improvements/ Fri, 25 Feb 2022 00:00:00 +1200 <p>This project was part of Google Summer of Code 2021. The development report can be found on the <a href="https://wiki.freebsd.org/SummerOfCode2021Projects/SoundMixerImprovements">FreeBSD Wiki</a>. The reason behind this project is that the FreeBSD&rsquo;s OSS mixer capabilities were really basic and outdated &mdash; even un/muting didn&rsquo;t exist and one had to write custom scripts for such a basic task. Setting default audio devices had to be done by tweaking sysctls and programs needing to use the mixer required DIY implementations as there was no mixer library available. The project is part of FreeBSD 14.0.</p> <h2 id="table-of-contents">Table of contents</h2> <ul> <li><a href="#kernel-patches">Kernel patches</a> <ul> <li><a href="#un-muting">Un/muting</a></li> <li><a href="#mode-configuration">Playback/recording mode information</a></li> </ul> </li> <li><a href="#userland">Userland</a> <ul> <li><a href="#libmixer-implementation">mixer(3) implementation</a></li> <li><a href="#mixer-rewrite">mixer(8) rewrite</a></li> </ul> </li> <li><a href="#code-and-manuals">Code and manuals</a></li> </ul> <h2 id="kernel-patches">Kernel patches</h2> <h3 id="un-muting">Un/muting (<a href="https://cgit.freebsd.org/src/commit/?id=0f8dafb45859569aa36b63ca2bb4a1c35c970d1e">commit</a>)</h3> <p>I decided that un/muting is better to be implemented in sound(4) in order to avoid having to write daemons or use files. The way this works is by implementing the <code>SOUND_MIXER_READ_MUTE</code> and <code>SOUND_MIXER_WRITE_MUTE</code> ioctls, which <em>did</em> exist in older OSS implementations, but were considered obselete. One thing to note is that the functionality isn&rsquo;t the same as their old one. Older OSS versions had those 2 ioctls take/return an integer with a value of 0 or 1, which indicated whether the <em>whole</em> mixer is muted or not. My implementation takes/returns a bitmask that tells which devices are muted. This allows us to mute and unmute only the devices we want, instead of the whole mixer. If you&rsquo;re familiar with the <a href="http://manuals.opensound.com/developer/">OSS API</a>, this bitmask works the same way as <code>DEVMASK</code>, <code>RECMASK</code> and <code>RECSRC</code>.</p> <h3 id="mode-configuration">Playback/recording mode information (<a href="https://cgit.freebsd.org/src/commit/?id=ed2196e5df0c8b5b81563d2fffdcb32bb7ebe966">commit</a>)</h3> <p>Here I implemented a sysctl (<code>dev.pcm.&lt;N&gt;.mode</code>) which gives information about a device&rsquo;s playback/recording mode. The rationale for this control is to include <code>/dev/sndstat</code>&rsquo;s mixer information in the output of the new mixer(8). The sysctl can return the following values (NOTE: these values are OR&rsquo;ed together if more than one mode is supported):</p> <table> <thead> <tr> <th>Value</th> <th>Meaning</th> </tr> </thead> <tbody> <tr> <td>0x01</td> <td>Mixer</td> </tr> <tr> <td>0x02</td> <td>Playback</td> </tr> <tr> <td>0x04</td> <td>Recording</td> </tr> </tbody> </table> <h2 id="userland">Userland</h2> <h3 id="libmixer-implementation">mixer(3) implementation (<a href="https://cgit.freebsd.org/src/commit/?id=903873ce15600fc02a0ea42cbf888cff232b411d">commit</a>)</h3> <p>mixer(3) provides a simple interface for working with the OSS mixer. <a href="https://man.freebsd.org/cgi/man.cgi?query=mixer&amp;apropos=0&amp;sektion=3&amp;manpath=FreeBSD+15.0-CURRENT&amp;arch=default&amp;format=html">The man page</a> explains how the library works, including some examples, so there&rsquo;s no need to repeat myself. You can see the library in action in <a href="https://cgit.freebsd.org/src/tree/usr.sbin/mixer/mixer.c">the source code for mixer(8)</a>.</p> <p>The basic structure of a program looks like this (link with <code>-lmixer</code>):</p> <pre tabindex="0"><code>#include &lt;err.h&gt; #include &lt;mixer.h&gt; int main(int argc, char *argv[]) { struct mixer *m; const char *name = &#34;/dev/mixer0&#34;; if ((m = mixer_open(name)) == NULL) err(1, &#34;mixer_open(%s)&#34;, name); /* do stuff */ mixer_close(m); return (0); } </code></pre><h3 id="mixer-rewrite">mixer(8) rewrite (<a href="https://cgit.freebsd.org/src/commit/?id=903873ce15600fc02a0ea42cbf888cff232b411d">commit</a>)</h3> <p>This implementation is a complete rewrite of the old mixer(8) utility. It now uses mixer(3) as a backend and implements all the new features the library provides. It&rsquo;s got more command line options and works with a control-oriented interface inspired by <a href="https://man.openbsd.org/mixerctl">OpenBSD&rsquo;s mixerctl(8)</a>. Again, everything is detailed in <a href="https://man.freebsd.org/cgi/man.cgi?query=mixer&amp;apropos=0&amp;sektion=8&amp;manpath=FreeBSD+15.0-CURRENT&amp;arch=default&amp;format=html">the man page</a>.</p> <p>Old mixer(8) output:</p> <pre tabindex="0"><code>$ mixer.old Mixer vol is currently set to 85:85 Mixer pcm is currently set to 100:100 Mixer speaker is currently set to 74:74 Mixer line is currently set to 1:1 Mixer mic is currently set to 67:67 Mixer mix is currently set to 74:74 Mixer rec is currently set to 37:37 Mixer igain is currently set to 0:0 Mixer ogain is currently set to 100:100 Mixer monitor is currently set to 67:67 Recording source: mic </code></pre><p>New mixer(8) output:</p> <pre tabindex="0"><code>$ mixer pcm0:mixer: &lt;Realtek ALC662 rev3 (Analog 2.0+HP/2.0)&gt; on hdaa0 kld snd_hda (play/rec) (default) vol = 0.85:0.85 pbk pcm = 1.00:1.00 pbk speaker = 0.74:0.74 rec line = 0.01:0.01 rec mic = 0.67:0.67 rec src mix = 0.74:0.74 rec rec = 0.37:0.37 pbk igain = 0.00:0.00 pbk ogain = 1.00:1.00 pbk monitor = 0.67:0.67 rec </code></pre><h2 id="code-and-manuals">Code and manuals</h2> <ul> <li><a href="https://man.freebsd.org/cgi/man.cgi?query=mixer&amp;apropos=0&amp;sektion=3&amp;manpath=FreeBSD+15.0-CURRENT&amp;arch=default&amp;format=html">mixer(3) man page</a></li> <li><a href="https://cgit.freebsd.org/src/tree/lib/libmixer">mixer(3) source code</a></li> <li><a href="https://man.freebsd.org/cgi/man.cgi?query=mixer&amp;apropos=0&amp;sektion=8&amp;manpath=FreeBSD+15.0-CURRENT&amp;arch=default&amp;format=html">mixer(8) man page</a></li> <li><a href="https://cgit.freebsd.org/src/tree/usr.sbin/mixer/">mixer(8) source code</a></li> <li><a href="http://manuals.opensound.com/developer/">OSS 4.x Programmer&rsquo;s Guide</a></li> </ul> Thermometer using PIC16F877A and BME280 https://margiolis.net/w/pic_therm/ Mon, 14 Feb 2022 00:00:00 +1200 <p>This embedded system measures temperature and humidity and outputs the data on an LCD. The BME280 sensor can also do pressure but I didn&rsquo;t implement it because the MCU had no more space available. <a href="https://git.sr.ht/~crm/pic_therm">The whole project can be found here</a>. I&rsquo;ve also included datasheets for the MCU, sensor and LCD.</p> <p>Development was done on FreeBSD using <a href="https://margiolis.net/w/pic_freebsd">the workflow I describe here</a>. If you&rsquo;re using Microchip&rsquo;s own tools, you&rsquo;ll have to make some small changes to the code, but nothing too extreme.</p> <p><a href="https://git.sr.ht/~crm/pic_therm/tree/master/item/src">The source code</a>.</p> <p><img src="https://margiolis.net/files/pic_therm_main.webp" alt="pic_therm_main"></p> <h2 id="components">Components</h2> <ul> <li>Microchip PIC16F877A-I/P microcontroller.</li> <li>Adafruit BME280 temperature, humidity and pressure sensor.</li> <li>16x2 LCD.</li> <li>1x 16MHz crystal oscillator.</li> <li>2x 10kΩ resistor.</li> <li>2x 300Ω resistor.</li> <li>1x 10kΩ potentiometer.</li> <li>2x 22pF ceramic capacitor.</li> <li>2x LED.</li> <li>2x push-button.</li> <li>Breadboard and wires.</li> <li>3x AAA batteries (4.5V total) or a 9V battery with a 5V voltage divider.</li> </ul> <h2 id="safe-temperature-range">Safe temperature range</h2> <table> <thead> <tr> <th>Component</th> <th>Operating temperature</th> </tr> </thead> <tbody> <tr> <td>PIC16F877A</td> <td>-40°C - 85°C</td> </tr> <tr> <td>BME280</td> <td>-40°C - 85°C</td> </tr> <tr> <td>LCD</td> <td>-20°C - 70°C</td> </tr> </tbody> </table> <p>So, it&rsquo;s best for the system to operate in the -20°C to 70°C range.</p> <h2 id="schematic">Schematic</h2> <p>You can also get the <a href="https://git.sr.ht/~crm/pic_therm/tree/master/item/schem/pic.pdf">PDF version</a>.</p> <p><img src="https://margiolis.net/files/pic_therm_schem.webp" alt="pic_therm_schem"></p> PIC microcontroller development on FreeBSD https://margiolis.net/w/pic_freebsd/ Sun, 23 Jan 2022 00:00:00 +1200 <p>Tested on FreeBSD 13.0. This article has also been mirrored on the <a href="https://wiki.freebsd.org/Microcontrollers/PIC">FreeBSD Wiki</a>.</p> <h2 id="prerequisites">Prerequisites</h2> <p><a href="http://sdcc.sourceforge.net/">sdcc</a> is a C compiler for microprocessors. It says that PIC microprocessors are unmaintained, but I&rsquo;ve found it to be pretty reliable so far (take this with a grain of salt, I&rsquo;m no expert). The FreeBSD port is <code>lang/sdcc</code>.</p> <p>Page 75 of the <a href="http://sdcc.sourceforge.net/doc/sdccman.pdf">sdcc user manual</a> lists the supported PIC devices. Header files can be found under <code>/usr/local/share/sdcc</code>.</p> <p>For programming the MCU, I&rsquo;ve found pk2cmd to work alright with PICKit2 (or Chinese clones), but there&rsquo;s no port for FreeBSD anymore. The Makefile won&rsquo;t install files properly, so we have some extra work to do afterwards:</p> <pre tabindex="0"><code>$ git clone https://github.com/psmay/pk2cmd.git $ cd pk2cmd/pk2cmd # gmake freebsd install clean # mv /usr/share/pk2/PK2DeviceFile.dat /usr/local/bin # rm -rf /usr/share/pk2 </code></pre><p>Supported devices for pk2cmd are listed <a href="https://github.com/psmay/pk2cmd/blob/master/pk2cmd/ReadmeForPK2CMDLinux2-6.txt">here</a>.</p> <h2 id="detecting-and-programming-the-mcu">Detecting and programming the MCU</h2> <p>Avoid using just the -P option to auto-detect the MCU, as the VPP the PICKit2 applies to the chip trying to detect it can damage the MCU. Instead, use the chip number beforehand as shown below. Also, use the -C option to check if the chip is blank.</p> <p>If any of the following pk2cmd commands fail, make sure everything <em>really is</em> wired properly:</p> <pre tabindex="0"><code>$ pk2cmd -P PIC16F877A -C Device is blank Operation Succeeded </code></pre><p>Compile your source code. The target executable is the <code>.hex</code> file sdcc will output. Replace <code>pic14</code> and <code>16f877a</code> with the appropriate names for your device:</p> <pre tabindex="0"><code>$ sdcc --use-non-free -mpic14 -p16f877a main.c </code></pre><p>Erase the PIC (if it wasn&rsquo;t already blank) and flash the new code. Again, use the appropriate names:</p> <pre tabindex="0"><code>$ pk2cmd -P PIC16F877A -E $ pk2cmd -P PIC16F877A -X -M -F main.hex </code></pre><p>If all went well, you should get an output similar to this:</p> <pre tabindex="0"><code>PICkit 2 Program Report 23-1-2022, 21:01:29 Device Type: PIC16F877A Program Succeeded. Operation Succeeded </code></pre> Email-driven Git workflow https://margiolis.net/w/email_git/ Sun, 16 Jan 2022 00:00:00 +1200 <p>Basic Git configuration (in case you haven&rsquo;t already done that):</p> <pre tabindex="0"><code>$ git config --global user.email you@example.org $ git config --global user.name &#34;Your Name&#34; </code></pre><p>Git email configuration:</p> <pre tabindex="0"><code>$ git config --global sendemail.smtpserver your_mail_server $ git config --global sendemail.smtpuser you@example.org $ git config --global sendemail.smtpserverport your_smtp_port $ git config --global sendemail.smtpencryption your_encryption_type </code></pre><h2 id="examples">Examples</h2> <p>Apply a patch (or simply make a commit) and send it to a mailing list:</p> <pre tabindex="0"><code>$ git am &lt; some_patch $ git send-email --to=list@example.org HEAD^ </code></pre><p>Fix last commit and send it:</p> <pre tabindex="0"><code>$ git commit -a --amend $ git send-email --annotate -v2 HEAD^ </code></pre><p>Send 3 last commits (see <a href="https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection">Revision Selection</a> for more info on the notation):</p> <pre tabindex="0"><code>$ git send-email HEAD~3 </code></pre><p>Send the last commit to list@example.org and make the subject look like &ldquo;<code>[reponame][PATCH] commitmsg</code>&rdquo;. This is useful for sending patches to mailing lists or programmers with multiple projects:</p> <pre tabindex="0"><code>$ git send-email --subject-prefix=&#34;${PWD##*/}][PATCH&#34; \ --to=list@example.org -1 </code></pre><p>Simple as that. None of that fork &amp; pull request crap.</p> <h2 id="further-reading">Further reading</h2> <ol> <li><a href="https://begriffs.com/posts/2018-06-05-mailing-list-vs-github.html">Mailing lists vs GitHub</a></li> <li><a href="https://web.archive.org/web/20180522180815/https://dpc.pw/blog/2017/08/youre-using-git-wrong/">You&rsquo;re using Git wrong</a></li> <li><a href="https://drewdevault.com/2018/07/02/Email-driven-git.html">The advantages of an email-driven Git workflow</a></li> <li><a href="https://git-send-email.io/">https://git-send-email.io/</a></li> <li><a href="https://linux.die.net/man/1/git-send-email">git-send-email(1) man page</a></li> </ol> How to jump start your car https://margiolis.net/w/jumpstart/ Wed, 12 Jan 2022 00:00:00 +1200 <p>There are tons of articles and videos out there already, but all of them are too long or AI-generated.</p> <p>Requirements: jumper cables and a live car. Order matters.</p> <p><img src="https://margiolis.net/files/carpush.webp" alt="carpush"></p> <ol> <li>Turn off the live car&rsquo;s engine and remove the key. Make sure the emergency brake is on and the transmission in neutral. Also make sure that your cables are always apart and away from your body.</li> <li>Dead car: connect the positive (red) end of the cable to the battery&rsquo;s positive terminal. The terminals are distinguished from their colors (red for positive and black for negative) and/or their sign (+ for positive, - for negative).</li> <li>Live car: connect the other positive end of the cable to the battery&rsquo;s positive terminal</li> <li>Live car: connect the negative (black) end of the cable to the battery&rsquo;s negative terminal.</li> <li>Dead car: connect the other negative end of the cable to a ground, for example a bolt on your hood.</li> <li>Keep the cables away from hot components, such as the radiator or the engine.</li> <li>Start the live car and slightly rev up the engine every now and then. Do this for a few minutes.</li> <li>Dead (now alive) car: disconnect the ground.</li> <li>Live car: disconnect the negative terminal.</li> <li>Live car: disconnect the positive terminal.</li> <li>Dead car: disconnect the positive terminal.</li> </ol> <p><a href="https://youtu.be/28MexgFvq7E?t=7">DO NOT confuse the terminals</a> or you will destroy your car&rsquo;s computer system.</p> C coding style https://margiolis.net/w/cstyle/ Sat, 01 Jan 2022 00:00:00 +1200 <p>First of all, I wanna wish all 0 readers of this website a happy new year! There&rsquo;s no reason to write a whole article repeating what better articles have already covered, so here&rsquo;s a list with proper C coding style guides:</p> <ul> <li><a href="https://www.lysator.liu.se/c/pikestyle.html">Notes on Programming in C</a> by Rob Pike.</li> <li><a href="https://man.openbsd.org/style">OpenBSD style guide</a></li> <li><a href="https://suckless.org/coding_style/">suckless.org style guide</a></li> <li><a href="https://www.kernel.org/doc/Documentation/process/coding-style.rst">Linux kernel coding style</a></li> </ul> <p><img src="https://margiolis.net/files/dmr.webp" alt="dmr"></p> 9front using bhyve(8) on FreeBSD https://margiolis.net/w/9front_bhyve/ Tue, 23 Nov 2021 00:00:00 +1200 <p>This article is also mirrored on the <a href="https://wiki.freebsd.org/bhyve/9front">FreeBSD Wiki</a>. and <a href="http://wiki.9front.org/freebsd-bhyve">9front Wiki</a>.</p> <p>Required ports:</p> <pre tabindex="0"><code>sysutils/bhyve-firmware sysutils/uefi-edk2-bhyve sysutils/uefi-edk2-bhyve-csm net/tigervnc-viewer </code></pre><p>Add the following lines to <code>/etc/rc.conf</code>. Replace <code>re0</code> with your own network interface. It&rsquo;s good practice to assign each VM a unique <code>tap</code> interface in case you need to run multiple VMs at the same time. For simplicity&rsquo;s sake, this setup uses only one <code>tap</code>:</p> <pre tabindex="0"><code>if_bridge_load=&#34;YES&#34; if_tap_load=&#34;YES&#34; cloned_interfaces=&#34;bridge0 tap0&#34; ifconfig_bridge0=&#34;DHCP addm re0 addm tap0&#34; ifconfig_bridge0_alias0=&#34;inet 10.0.0.1/24&#34; </code></pre><p>Reboot your machine and then grab a <a href="http://9front.org/releases/">9front ISO</a>.</p> <p>Make a directory where you&rsquo;ll store everything 9front-related. I usually keep all my <a href="https://www.freebsd.org/cgi/man.cgi?query=bhyve&amp;sektion=8">bhyve(8)</a> VMs under a <a href="https://docs.freebsd.org/en/books/handbook/zfs/">ZFS dataset</a>:</p> <pre tabindex="0"><code>$ cd /path/to/vms/ $ mkdir 9front $ mv /path/to/9front_iso 9front.iso </code></pre><p>Create an empty file to be used as the VM&rsquo;s hard drive. 10G will be more than enough:</p> <pre tabindex="0"><code>$ truncate -s 10G disk.img </code></pre><p>Make a startup script. Feel free to tweak the variable values to match your own setup. Obviously, when you&rsquo;re done installing 9front from the ISO, you&rsquo;ll be running the script without the <code>-s 3,...</code> line:</p> <pre tabindex="0"><code>$ cat 9front_start #!/bin/sh name=&#34;9front&#34; cpu=&#34;2&#34; mem=&#34;2G&#34; iso=&#34;9front.iso&#34; disk=&#34;disk.img&#34; tap=&#34;tap0&#34; ifconfig ${tap} up bhyve -c ${cpu} -m ${mem} -wH \ -s 0,hostbridge \ -s 3,ahci-cd,${iso} \ -s 4,ahci-hd,${disk} \ -s 5,virtio-net,${tap} \ -s 29,fbuf,tcp=0.0.0.0:5900,w=800,h=600,wait \ -s 30,xhci,tablet \ -s 31,lpc \ -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd \ ${name} </code></pre><p>Make a shutdown script in order for bhyve(8) to shutdown properly:</p> <pre tabindex="0"><code>$ cat 9front_stop #!/bin/sh name=&#34;9front&#34; tap=&#34;tap0&#34; ifconfig ${tap} down bhyvectl --force-poweroff --vm=${name} bhyvectl --destroy --vm=${name} </code></pre><p>Make the scripts executable and start the VM:</p> <pre tabindex="0"><code>$ chmod +x 9front_start 9front_stop # ./9front_start; ./9front_stop </code></pre><p>Run vncviewer(1) to connect to the VNC display:</p> <pre tabindex="0"><code>$ vncviewer 0.0.0.0 </code></pre><p>When prompted for the monitor type during boot, choose <code>xga</code>.</p> Git Web frontend with stagit(1) https://margiolis.net/w/stagit_frontend/ Fri, 05 Nov 2021 00:00:00 +1200 <p><a href="https://codemadness.org/stagit.html">stagit(1)</a> reads Git repositories and generates static pages for them with a predefined CSS stylesheet (I said <em>stylesheet</em> twice!), a website logo and a favicon.</p> <p>Required:</p> <ul> <li><a href="https://margiolis.net/w/openbsd_web">A Web server</a></li> <li><a href="https://margiolis.net/w/openbsd_git">A Git Server</a></li> </ul> <p>Install stagit(1):</p> <pre tabindex="0"><code>$ git clone git://git.codemadness.org/stagit $ cd stagit # doas make install clean </code></pre><p>I like to have everything stagit(1)-related inside <code>/var/www/htdocs/git.example.org</code> along with the actual repositories, but you can choose any other directory.</p> <p>Store the stylesheet, logo and favicon inside. You&rsquo;ll find the stylesheet inside the stagit(1) repository you cloned earlier; it&rsquo;s called <code>style.css</code>. For the logo and favicon, just make your own, but make sure they are in PNG format and have the same names as below.</p> <pre tabindex="0"><code># cp logo.png favicon.png style.css /var/www/htdocs/git.example.org </code></pre><p>You can edit the stylesheet to your likings, but if you&rsquo;re afraid that you&rsquo;ll mess things up, play around with the colors only.</p> <p><a href="https://margiolis.net/w/openbsd_git">Here</a> I created a repository named <code>repo</code> in <code>/var/www/htdocs/git.example.org/repo.git</code>. I will now make a directory to store the files that stagit(1) will produce. I like to give it the same name as the repository but without the <code>.git</code> extension:</p> <pre tabindex="0"><code># mkdir /var/www/htdocs/git.example.org/repo </code></pre><p>Create symlinks for the stylesheet, logo and favicon inside the directory you just created:</p> <pre tabindex="0"><code>$ cd /var/www/htdocs/git.example.org/repo # ln -s ../style.css ../logo.png ../favicon.png . </code></pre><p>Run stagit(1) and generate the static page for your repository. This has to be done inside the directory you just created since stagit(1) stores the files in the working directory. Make sure that you give it the <code>.git</code> directory (i.e., the actual repository):</p> <pre tabindex="0"><code># stagit ../repo.git </code></pre><p>Go to the top-level Git directory and generate an <code>index.html</code> file which is your Git server&rsquo;s homepage where all repositories will be listed. Again, you give it the actual repositories:</p> <pre tabindex="0"><code>$ cd /var/www/htdocs/git.example.org # stagit-index repo.git &gt; index.html </code></pre><p>I&rsquo;ve made a simple script to automate the generation process:</p> <pre tabindex="0"><code>#!/bin/sh cd /var/www/htdocs/git.example.org for dir in $(ls | grep &#34;git&#34;); do cd ${dir%.*} &amp;&amp; stagit ../${dir} &amp;&amp; cd .. &amp;&amp; echo &#34;updating ${dir}: ok&#34; done stagit-index $(ls | grep &#34;git&#34;) &gt; index.html &amp;&amp; echo &#34;creating index.html. ok&#34; </code></pre><p>Fire up a browser and go to <code>git.example.org</code> and you should see everything. Success!</p> OpenBSD Git server setup https://margiolis.net/w/openbsd_git/ Wed, 10 Feb 2021 00:00:00 +1200 <h2 id="git-subdomain">Git subdomain</h2> <p>It&rsquo;s good to have your Git server under a subdomain, so that it looks like <code>git.example.org</code>. To do that, add a new CNAME record for the subdomain on your registrar. If you&rsquo;re on Epik, don&rsquo;t forget the trailing period after the domain name.</p> <pre tabindex="0"><code>Host: git | Points to: example.org. </code></pre><h2 id="setting-up-the-git-server">Setting up the Git server</h2> <p>Install git(1):</p> <pre tabindex="0"><code># pkg_add git </code></pre><p>First create a <code>git</code> user and give it a password. If you don&rsquo;t want the same hosts that exist in your current user&rsquo;s <code>authorized_keys</code> to have access to the Git server, then add their keys manually in the <code>git</code> user&rsquo;s <code>.ssh/authorized_keys</code> file. In my case, it&rsquo;s just me who has access to the server, so I will not edit anything:</p> <pre tabindex="0"><code># adduser git ... # cp -r $HOME/.ssh /home/git/ # chown -R git:git /home/git/.ssh </code></pre><p>Try SSH&rsquo;ing to the server as <code>git</code> from your local computer just to test if it works:</p> <pre tabindex="0"><code>$ ssh git@example.org </code></pre><p>If it worked, exit the session.</p> <p>Set up a Git directory where all repositories will be stored at and <code>cd</code> into it:</p> <pre tabindex="0"><code># mkdir /var/www/htdocs/git.example.org # chown -R git:git /var/www/htdocs/git.example.org # cd /var/www/htdocs/git.example.org </code></pre><p>Say I want to create a repository named <code>repo</code>. The structure your repositories should have is <code>name.git</code>, so in this case it&rsquo;ll be <code>repo.git</code>. Create a bare repository inside it:</p> <pre tabindex="0"><code># mkdir repo.git # git init --bare repo.git </code></pre><p>Replace each text inside double quotes with what you want it to be:</p> <pre tabindex="0"><code># echo &#34;John Doe&#34; &gt; repo.git/.git/owner # echo &#34;This is a repo&#34; &gt; repo.git/.git/description # echo &#34;git://git.example.org/repo.git&#34; &gt; repo.git/.git/url </code></pre><p>To be able to interact with the Git server, the repository&rsquo;s owner needs to be the <code>git</code> user you created earlier:</p> <pre tabindex="0"><code># chown -R git:git repo.git </code></pre><p>Since sometimes the Git server by default might not be listening on port 9418 (the default Git port), which means that you&rsquo;ll not be able to clone repositories from the Git server, you have to set up the daemon yourself using a massive Git command. You can (in fact, you should) also use Git over HTTPS or SSH, but for now I&rsquo;ll be using the default Git protocol for cloning and SSH for pushing.</p> <pre tabindex="0"><code># git daemon \ --base-path=/var/www/htdocs/git.example.org/ \ --export-all \ --enable=receive-pack \ --reuseaddr \ --informative-errors \ --verbose \ --detach </code></pre><h2 id="pushing-changes-to-the-server">Pushing changes to the server</h2> <p>On your local machine, make a repository for <code>repo</code> (the repository you created earlier) and add a few files so that you have something to commit and push to the server:</p> <pre tabindex="0"><code>$ mkdir repo &amp;&amp; cd repo $ touch foo bar $ git init $ git add . $ git commit -m &#34;initial commit&#34; </code></pre><p>In order to be able to push you have to edit <code>.git/config</code>. If you&rsquo;ve used GitHub before, you might have noticed that when you want to push, you push to <code>origin</code>; for this server I&rsquo;ll use <code>home</code>, but you can use whatever else you want, it doesn&rsquo;t really matter.</p> <p>Append the following block to your repository&rsquo;s <code>.git/config</code> and of course replace the domain and repository names with your own ones:</p> <pre tabindex="0"><code>[remote &#34;home&#34;] url = git@git.example.org:/var/www/htdocs/git.example.org/repo.git fetch = +refs/heads/*:refs/remotes/home/* </code></pre><p>Push to the Git server:</p> <pre tabindex="0"><code>$ git push home master </code></pre><p>Try also cloning the repository to make sure that everything works:</p> <pre tabindex="0"><code>$ git clone git://git.example.org/repo.git </code></pre><p>If that step was successful too, great, you now have a working Git server. :-)</p> <p><a href="https://margiolis.net/w/stagit_frontend">Create a Web frontend with stagit(1)</a>.</p> OpenBSD Web server setup https://margiolis.net/w/openbsd_web/ Tue, 09 Feb 2021 00:00:00 +1200 <h2 id="https">HTTPS</h2> <p>acme-client(1) lets us have HTTPS.</p> <p>Create the certificate directories:</p> <pre tabindex="0"><code># mkdir -p -m 700 /etc/ssl/private # mkdir -p -m 755 /var/www/acme </code></pre><p>Edit <code>/etc/acme-client.conf</code>. Replace <code>example.org</code> with your domain.</p> <pre tabindex="0"><code>authority letsencrypt { api url &#34;https://acme-v02.api.letsencrypt.org/directory&#34; account key &#34;/etc/ssl/private/letsencrypt.key&#34; } domain example.org { domain key &#34;/etc/ssl/private/example.org.key&#34; domain certificate &#34;/etc/ssl/example.org.crt&#34; domain full chain certificate &#34;/etc/ssl/example.org.pem&#34; sign with letsencrypt } </code></pre><h2 id="httpd8">httpd(8)</h2> <p>OpenBSD already ships with a web server: httpd(8). You need to specify the following things:</p> <ul> <li>The domain name.</li> <li>Which port/s the server will listen to.</li> <li>Where the website&rsquo;s root directory is.</li> </ul> <p>Create the website&rsquo;s root directory. It should normally reside under <code>/var/www/htdocs</code>.</p> <pre tabindex="0"><code># mkdir -p /var/www/htdocs/example.org </code></pre><p>The following configuration will suffice for now. With it you have HTTP and HTTPS for your website and it redirects HTTP to HTTPS automatically. If you want to learn more or see what other options and settings are available, read <a href="https://man.openbsd.org/httpd.conf.5">httpd.conf(5)&rsquo;s man page</a>. Inside <code>/etc/httpd.conf</code> we&rsquo;ll write the following:</p> <pre tabindex="0"><code>server &#34;example.org&#34; { listen on * port 80 root &#34;/htdocs/example.org&#34; location &#34;/.well-known/acme-challenge/*&#34; { root &#34;/acme&#34; request strip 2 } #block return 301 &#34;https://example.org$REQUEST_URI&#34; } server &#34;example.org&#34; { listen on * tls port 443 root &#34;/htdocs/example.org&#34; tls { certificate &#34;/etc/ssl/example.org.pem&#34; key &#34;/etc/ssl/private/example.org.key&#34; } location &#34;/.well-known/acme-challenge/*&#34; { root &#34;/acme&#34; request strip 2 } } </code></pre><p>Generate the TLS certificates:</p> <pre tabindex="0"><code># acme-client -v example.org </code></pre><p>Test to see if the configuration is correct:</p> <pre tabindex="0"><code># httpd -n </code></pre><p>(Re)start the web server:</p> <pre tabindex="0"><code># rcctl restart httpd </code></pre><h2 id="certificate-renewal">Certificate renewal</h2> <p>TLS certificates expire after a few months so new certificates need to be generated when they expire. In order to avoid having to remember this and having to manually generate them, a cronjob will do it automatically:</p> <pre tabindex="0"><code># crontab -e </code></pre><p>Append the following line:</p> <pre tabindex="0"><code>0 0 * * * acme-client -v example.org &amp;&amp; rcctl reload httpd </code></pre> Use cases for goto https://margiolis.net/w/goto/ Tue, 19 Jan 2021 00:00:00 +1200 <p>This article is for everyone, who, for some reason, thinks <code>goto</code> should be avoided at all costs.</p> <h2 id="some-use-cases">Some use cases</h2> <p>The most important use case there is for <code>goto</code> is by far error handling when there are more than 1 points of failure. In this case, you might want to cleanup some resources while also skipping part of the code that should not be executed, without having to deal with flags, helper functions, and other methods that would make the code ugly, slower and error prone. Try rewriting the following snippet <em>without</em> a <code>goto</code>:</p> <pre tabindex="0"><code>int foo(int *bar, int *baz) { if (!func1()) goto fail; if (!func2()) goto fail; if (!func3()) goto fail; return 0; fail: warn(&#34;foo failed&#34;); if (bar != NULL) free(bar); if (baz != NULL) free(baz); return -1 } </code></pre><p>Another use case is breaking out of deeply nested code. Let&rsquo;s say you&rsquo;ve got 3 <code>for</code> loops and there&rsquo;s a special case in which you really want to break out of all the loops at once. how do you do that? There are multiple ways you can go about doing so but one way would be to set a flag and check it on every nested level.</p> <pre tabindex="0"><code>flag = 0; for (i = 0; i &lt; 10; i++) { for (j = 0; j &lt; 10; j++) { for (k = 0; k &lt; 10; k++) { ... if (flag) break; } if (flag) break; } if (flag) break; } </code></pre><p>Another ugly hack you can use is something another colleague from university showed me, and something I would <em>never</em> use; when the flag is set, manually max out all the loop counters.</p> <p>A pretty straight-forward solution would also be to put the loop into a function and use a <code>return</code> statement to break out of all the loops. That&rsquo;s actually a good solution, and I&rsquo;m aware of it, but I want to provide another solution, which is also quite faster than using a function since it avoids that additional function call.</p> <p>An alternative, and in my opinion, better way of solving this problem would be by using a (<em>don&rsquo;t say it, don&rsquo;t say it</em>) <code>goto</code>:</p> <pre tabindex="0"><code>flag = 0; for (i = 0; i &lt; 10; i++) { for (j = 0; j &lt; 10; j++) { for (k = 0; k &lt; 10; k++) { ... if (flag) goto end; } } } end: ... </code></pre><h2 id="who-cares-anyway">Who cares, anyway?</h2> <p>In the first use case, the code is much more readable and you avoid code duplication. In the second use case the <code>goto</code> solution actually <em>does</em> improve performance. The reason why is simple; we check for <code>flag</code> on every single loop, which means, that in case <code>flag</code> is never set, we&rsquo;ll have done 10 * 10 * 10 = 1000 checks just to see if <code>flag</code> is set. And that&rsquo;s just with 3 <code>for</code> loops going from 0 to 10 each; think how easily this can scale up if you just increase the iterations. The <code>goto</code> solution does only <em>one</em> check in the third loop, which means that, in the above scenario, where <code>flag</code> never gets set, we&rsquo;ll have done only 10 checks - that&rsquo;s 100 times faster than the other solution.</p> <p>Using a function is almost just as fast as using a <code>goto</code> without a function, but not having to call a function is generally faster. Both solutions are great and totally valid, I just want to show an alternative one.</p> <h2 id="final-note">Final note</h2> <p><code>goto</code> <em>does</em> have its place but it should be used carefuly; if you overuse it, your code will either become incomprehensible, or flat out broken. The use cases I showcased in this post are very common and sometimes the code can be vastly improved with just a simple <code>goto</code> if used correctly.</p> Sync files with rsync(1) https://margiolis.net/w/rsync/ Sun, 22 Nov 2020 00:00:00 +1200 <h2 id="connecting-local-machines">Connecting local machines</h2> <p>In order to connect two machines on the same network, they both need to know each other&rsquo;s network IP address. To get your network IP address execute the following command on each of the local machines you want to connect:</p> <pre tabindex="0"><code>$ ifconfig </code></pre><p>In your running network interface&rsquo;s section, <code>ifconfig</code> will output a line that looks like this:</p> <pre tabindex="0"><code>inet 192.168.x.y </code></pre><p>This is your network IP address which the other machine needs to know. Suppose you want to connect two local machines at home; we&rsquo;ll call the first one <code>host1</code> and the other <code>host2</code>. the ip address for <code>host1</code> is <code>192.168.1.7</code> and for <code>host2</code> it&rsquo;s <code>192.168.1.8</code>. add the following line on <code>host1</code>&rsquo;s <code>/etc/hosts</code> so that it knows who <code>host2</code> is. do the same for <code>host2</code>.</p> <pre tabindex="0"><code>192.168.1.8 host2 </code></pre><p>After completing this step ping each other using both the IP and the hostname to make sure everything works:</p> <pre tabindex="0"><code>$ ping 192.168.1.8 ping 192.168.1.8 (192.168.1.8): 56 data bytes 64 bytes from 192.168.1.8: icmp_seq=0 ttl=64 time=36.931 ms ... $ ping host2 ping host2 (192.168.1.8): 56 data bytes 64 bytes from 192.168.1.8: icmp_seq=0 ttl=64 time=74.056 ms ... </code></pre><p>If you don&rsquo;t already have an SSH key pair, generate one:</p> <pre tabindex="0"><code>$ ssh-keygen </code></pre><p>Copy the SSH public key over to the other machine:</p> <pre tabindex="0"><code>$ eval $(ssh-agent -s) $ ssh-add $ ssh-copy-id user@host2 </code></pre><p>Repeat the same process for <code>host2</code>. to verify that the key was indeed copied over, look at <code>~/.ssh/authorized_keys</code>.</p> <h2 id="using-rsync">Using rsync</h2> <p>You can think of rsync(1) as cp(1) with superpowers; in fact, you could even alias <code>cp</code> to <code>rsync</code>.</p> <p>To just copy a file on your machine you can do exactly the same thing you would do with <code>cp</code>:</p> <pre tabindex="0"><code>$ rsync foo bar/ </code></pre><p>Suppose we want to send that file to <code>host2</code> (read the previous section):</p> <pre tabindex="0"><code>$ rsync foo host2: </code></pre><p>By default, rsync(1) assumes that the target user has the same name as yours. In case it&rsquo;s different, you need to specify it:</p> <pre tabindex="0"><code>$ rsync foo user@host2: </code></pre><p>The file will be copied in the other machine&rsquo;s home directory, if no other directory is specified. To send it to a specific path:</p> <pre tabindex="0"><code>$ rsync foo user@host2:foo/ </code></pre><p>rsync(1) has <em>a lot</em> of options, but some of the most common ones, and the ones I use most of the time are the following:</p> <dl> <dt><code>-r</code></dt> <dd>Copy directories recursively.</dd> <dt><code>-u</code></dt> <dd>Update only the files that have changed (incremental copy).</dd> <dt><code>-v</code></dt> <dd>Increase verbosity.</dd> <dt><code>-a</code></dt> <dd>Preserve permissions and metadata. does not preserve hard links.</dd> <dt><code>-p</code></dt> <dd>In case something goes wrong and the sync breaks, continue the sync from where it stopped.</dd> <dt><code>--delete-after</code></dt> <dd>Delete the old files after the transfer.</dd> </dl> <p><strong>Note</strong>: when you want to transfer a directory, you <em>must</em> add a <code>/</code> after its name (e.g <code>foo/</code>), otherwise rsync(1) will skip it.</p> <h2 id="sample-usage">Sample usage</h2> <p>Suppose I want to update a website that is hosted on a remote server. I can make changes locally and just sync it with the server whenever I want using <code>rsync</code>. Let&rsquo;s say the remote directory is located at <code>/var/www/website</code>:</p> <pre tabindex="0"><code>$ rsync -purv --delete-after website/ user@server:/var/www/website </code></pre><p>You can automate this process using a simple shell script. In this case though, you have to pass the absolute paths of the directories so that you can run the script from everywhere:</p> <pre tabindex="0"><code>#!/bin/sh src=&#34;/foo/bar/website/&#34; dst=&#34;user@server:/var/www/website&#34; rsync -purv --delete-after ${src} ${dst} </code></pre> Arduino on FreeBSD https://margiolis.net/w/arduino_freebsd/ Wed, 28 Oct 2020 00:00:00 +1200 <p>This article demonstrates how to develop for Arduino boards using only basic command line utilities, without having to use the Arduino IDE. The article has also been published on the <a href="https://wiki.freebsd.org/Arduino/NativeCLI">FreeBSD Wiki</a>.</p> <p>Tested on FreeBSD 12.2 and above.</p> <h2 id="prerequisites">Prerequisites</h2> <p>Required ports:</p> <pre tabindex="0"><code>devel/arduino-core devel/arduino-bsd-mk devel/avr-gcc devel/avr-libc devel/avrdude comms/uarduno </code></pre><p>With all the software installed, add the following line to <code>/boot/loader.conf</code> in case you want the Arduino kernel module to load automatically on boot. If you want to manually load the module whenever you need it, skip this step:</p> <pre tabindex="0"><code>uarduno_load=&#34;YES&#34; </code></pre><p>Load the kernel module:</p> <pre tabindex="0"><code># kldload uarduno </code></pre><p>Check your <code>~/.arduino/preferences.txt</code> and see if the following lines exist (<a href="https://wiki.freebsd.org/Arduino/NativeIDE">source</a>):</p> <pre tabindex="0"><code>serial.port=/dev/cuaU0 launcher=/usr/local/bin/firefox </code></pre><p>Add your user to the <code>dialer</code> group:</p> <pre tabindex="0"><code># pw group mod dialer -m $USER </code></pre><h2 id="connecting-the-board">Connecting the board</h2> <p>Standard Arduino boards connect as <code>/dev/cuaU0</code> and/or <code>/dev/ttyU0</code> on FreeBSD. In case these serial ports don&rsquo;t show up in <code>/dev</code>, you might need to press your board&rsquo;s reset button. After you&rsquo;ve plugged your board into a USB port, you should get the following output from dmesg(8). Although the output may vary, the important thing is that your board is connected and detected.</p> <pre tabindex="0"><code>ugen1.5: &lt;Arduino (www.arduino.cc) product 0x0043&gt; at usbus1 uarduno0: &lt;Arduino (www.arduino.cc) product 0x0043, class 2/0, rev 1.10/0.01, addr 5&gt; on usbus1 </code></pre><p>If <code>dmesg</code> returned information about your board, you should also see <code>cuaU0</code> and/or <code>ttyU0</code> in <code>/dev</code>. In case your board is still not detected &mdash; considering it&rsquo;s not a fake one, try using a different USB cable or reset it again and make sure you&rsquo;ve followed the setup steps correctly.</p> <h2 id="the-makefile">The Makefile</h2> <p>The only thing you&rsquo;re going to need in order to get started is just a <code>Makefile</code> that&rsquo;ll be used to compile <em>and upload</em> your Arduino programs. Make a new directory for your Arduino project and a <code>Makefile</code> with the following lines:</p> <pre tabindex="0"><code>ARDUINO_DIR= /usr/local/arduino ARDUINO_MK_DIR= /usr/local/arduino-bsd-mk #ARDUINO_LIBS= AVRDUDE_PORT= your_board_port ARDUINO_BOARD= your_board_name SRCS= your_source_files TARGET= your_program_name include /usr/local/arduino-bsd-mk/bsd.arduino.mk </code></pre><p>In my case my board is an Arduino Uno, so I&rsquo;d have to set <code>ARDUINO_BOARD</code> to <code>uno</code>. You can see which other board types are available in <code>/usr/local/arduino/hardware/arduino/avr/boards.txt</code>. If you want to install new libraries, copy them over to <code>/usr/local/arduino/hardware/arduino/avr/libraries/</code>.</p> <p>Avoid having source files named <code>main</code>.</p> <h2 id="building-and-uploading-a-program">Building and uploading a program</h2> <p>Write some Arduino code, and when you&rsquo;re ready to compile and upload, run the following command:</p> <pre tabindex="0"><code># make install flash clean cleandepend </code></pre><p>If all went well you should see the board executing the new code. If it doesn&rsquo;t, try to see what errors the <code>Makefile</code> produced.</p> <h2 id="monitoring">Monitoring</h2> <p>The Arduino IDE provides a serial monitor feature, but FreeBSD has a builtin monitoring utility which can be accessed directly from the terminal. Run this whenever you want to monitor your board and exit with <code>~!</code> (use the appropriate port):</p> <pre tabindex="0"><code>$ cu -l /dev/cuaU0 </code></pre><h2 id="using-board-types-other-than-the-uno">Using board types other than the Uno</h2> <p>As it&rsquo;s mentioned above, we&rsquo;re using the <code>uarduno</code> kernel module. Even though the module&rsquo;s description is <em>&ldquo;FreeBSD Kernel Driver for the Arduino Uno USB interface&rdquo;</em>, you can, in fact, use different board types other than the Uno. According to <code>uarduno</code>&rsquo;s <a href="http://www.mrp3.com/uarduno.html">website</a>, you can modify <code>/usr/ports/comms/uarduno/files/ids.txt</code> to include more board types; the two fields are Vendor ID and Product ID. Read the comments inside the file for more information.</p> <pre tabindex="0"><code>{ 0x2341, 0x0001 }, // Arduino UNO, vendor 2341H, product 0001H { 0x2341, 0x0042 }, // Arduino MEGA (rev 3), vendor 2341H, product 0042H { 0x2341, 0x0043 }, // Arduino UNO (rev 3), vendor 2341H, product 0043H { 0x2341, 0x0010 }, // Arduino MEGA 2560 R3, vendor 2341H, product 0010H { 0x2341, 0x8037 }, // Arduino Micro </code></pre><p>When you&rsquo;re done, clean and re-build the port.</p> <h2 id="known-issues-and-their-fixes">Known issues and their fixes</h2> <p>Even though you might have plugged your board to your machine, you might notice that there is no device appearing in <code>/dev</code>. Although there is no definite answer as to why this is happening, make sure that the USB cable is connected properly; on some boards, you have to hear a click sound.</p> <p>When trying to use a new library, you might notice that your code doesn&rsquo;t compile. A common issue is that you haven&rsquo;t stored the library in the correct path. As mentioned, libraries are stored in <code>/usr/local/arduino/hardware/arduino/avr/libraries/</code>, so you have to move it there.</p> Simple Brainfuck interpreter in C https://margiolis.net/w/brainfuck/ Wed, 02 Sep 2020 00:00:00 +1200 <h2 id="how-brainfuck-works">How Brainfuck works</h2> <p>There are only 8 symbols supported in Brainfuck:</p> <table> <thead> <tr> <th>Symbol</th> <th>Function</th> </tr> </thead> <tbody> <tr> <td>&gt;</td> <td>Increase position of pointer.</td> </tr> <tr> <td>&lt;</td> <td>Decrease position of pointer.</td> </tr> <tr> <td>+</td> <td>Increase value of pointer.</td> </tr> <tr> <td>-</td> <td>Decrease value of pointer.</td> </tr> <tr> <td>[</td> <td>Beginning of loop.</td> </tr> <tr> <td>]</td> <td>End of loop.</td> </tr> <tr> <td>.</td> <td>Output ASCII code of pointer.</td> </tr> <tr> <td>,</td> <td>Read a character and store its ASCII value in pointer.</td> </tr> </tbody> </table> <p>It&rsquo;s best to imagine Brainfuck programs as arrays of integers, which the pointer can manipulate. Let&rsquo;s say this is our initial state:</p> <pre tabindex="0"><code>{ 0, 0, 0, 0, 0, 0 } </code></pre><p>We can assign values to each position in the array by moving the pointer around. Using the + and - symbols, we can increment or decrement by 1 each time. If we wanted to move the pointer <em>two times to the right</em> and increment the value there by 3, we would have a program that looks like this:</p> <pre tabindex="0"><code>&gt;&gt;+++ </code></pre><p>The updated version of the array:</p> <pre tabindex="0"><code>{ 0, 0, 3, 0, 0, 0 } </code></pre><p>Following the same logic, we can assign specific values to each cell and make an actual program. If we wanted to print the letter &ldquo;B&rdquo; on the screen, which corresponds to the ASCII value 66, we could write the following program:</p> <pre tabindex="0"><code>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++. </code></pre><p>In order to avoid writing things like this we can use loops. The loop is executed as long as the value inside it is not 0. Essentially, it&rsquo;s going to do the multiplication 10 x 6 = 66 and then print the value:</p> <pre tabindex="0"><code>+++++ +++++ # add 10 to cell #0 [ # beginning of loop &gt; +++ +++ # add 6 to cell #1 &lt; - # subtract 1 from cell #0 ] # end of loop # value at cell 1 is now 66 (10 x 6 = 66) &gt; . # go to cell 1 and print its value </code></pre><p>Or, for compactness:</p> <pre tabindex="0"><code>+++++++++++[&gt;++++++&lt;-]&gt;. </code></pre><p>You can learn more about how Brainfuck works <a href="https://esolangs.org/wiki/Brainfuck">here</a>.</p> <h2 id="building-the-interpreter">Building the interpreter</h2> <p>We&rsquo;ll first read the Brainfuck source from <code>stdin</code> into a static size buffer. 50.000 bytes should be large enough to store any Brainfuck program, since I doubt anyone is mad enough to write actual programs in it:</p> <pre tabindex="0"><code>#define BUFSIZE 50000 . . . size_t len = 0; char buf[BUFSIZE]; while (read(STDIN_FILENO, &amp;buf[len], 1) &gt; 0) len++; buf[len] = &#39;\0&#39;; </code></pre><p>We&rsquo;ll declare the rest of the needed variables:</p> <pre tabindex="0"><code>int closed; /* number of active loops */ int opened; /* number of inactive loops */ int pos = 0; /* position in the program */ unsigned short *pc; /* program counter */ char *src; /* source code */ </code></pre><p>One of the reasons we have a <code>len</code> variable is to allocate just enough memory for <code>src</code>. We&rsquo;ll also empty the buffer because we now want to use it to store the values the Brainfuck program will produce:</p> <pre tabindex="0"><code>if ((src = malloc(len)) == NULL) { perror(&#34;malloc&#34;); exit(1); } strcpy(src, buf); memset(buf, 0, len); </code></pre><p>We can now parse the source code symbol by symbol. <code>pc</code> will act as the &ldquo;pointer&rdquo;, moving back and forth in the array. Each symbol will have its own case inside the following <code>switch</code> statement:</p> <pre tabindex="0"><code>for (pc = (unsigned short *)buf, pos = 0; pos &lt; len; pos++) { switch (src[pos]) { ... } } </code></pre><p>For the <code>&lt;</code> and <code>&gt;</code> symbols we simply move the pointer:</p> <pre tabindex="0"><code>case &#39;&gt;&#39;: pc++; break; case &#39;&lt;&#39;: pc--; break; </code></pre><p>The + and - symbols in/decrement the value of the cell the pointer is currently at:</p> <pre tabindex="0"><code>case &#39;+&#39;: (*pc)++; break; case &#39;-&#39;: (*pc)--; break; </code></pre><p>To implement the <code>.</code> and <code>,</code> symbols we&rsquo;ll use the standard library&rsquo;s <code>putchar()</code> and <code>getchar()</code> functions:</p> <pre tabindex="0"><code>case &#39;.&#39;: putchar(*pc); break; case &#39;,&#39;: *pc = getchar(); break; </code></pre><p>Now comes the last, but harder part, which is to implement loops. The logic behind my implementation is that instead of keeping track of every bracket to know where a loop starts and ends, the program keeps going through the source code and, using a counter, we know that a loop starts or ends when that counter is 0 <em><strong>and</strong></em> and an opposite bracket has been found. Also, in each iteration the <code>pos</code> variable changes accordingly so that we can imitate the looping behavior of Brainfuck.</p> <p>These are the steps the program follows for each of the two symbols:</p> <h2 id="beginning-of-loop-">Beginning of loop: <code>[</code></h2> <ul> <li>If the pointer&rsquo;s value <em>is</em> 0, we have a new loop.</li> <li>Count how many (if any) nested loops we encounter.</li> <li>If we encouter a <code>[</code>, increment the <code>opened</code> variable.</li> <li>If we encouter a <code>]</code>, decrement the <code>opened</code> variable.</li> <li>Keep counting until there are no new active loops (i.e., <code>opened</code> is 0).</li> </ul> <pre tabindex="0"><code>case &#39;[&#39;: if (!(*pc)) { for (opened = 0; pos++; pos &lt; len; pos++) { if (src[pos] == &#39;]&#39; &amp;&amp; !opened) break; else if (src[pos] == &#39;[&#39;) opened++; else if (src[pos] == &#39;]&#39;) opened--; } } break; </code></pre><h2 id="end-of-loop-">End of loop: <code>]</code></h2> <ul> <li>If the pointer&rsquo;s value is <em>is not</em> 0, we have an active loop.</li> <li>Start going back and count how many (if any) nested loops there are.</li> <li>If we encouter a <code>]</code>, increment the <code>closed</code> variable.</li> <li>If we encouter a <code>[</code>, decrement the <code>closed</code> variable.</li> <li>Keep counting until there are no active loops (i.e., <code>closed</code> is 0).</li> </ul> <pre tabindex="0"><code>case &#39;]&#39;: if ((*pc)) { for (closed = 0; pos--; pos &gt;= 0; pos--) { if (src[pos] == &#39;[&#39; &amp;&amp; !closed) break; else if (src[pos] == &#39;]&#39;) closed++; else if (src[pos] == &#39;[&#39;) closed--; } } break; </code></pre><h2 id="putting-it-all-together">Putting it all together</h2> <p>Below is the full program. You can also find it <a href="https://git.sr.ht/~crm/random/tree/master/item/bf">here</a>.</p> <pre tabindex="0"><code>#include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;string.h&gt; #include &lt;unistd.h&gt; #define BUFSIZE 50000 int main(int argc, char *argv[]) { size_t len = 0; int closed, opened, pos = 0; unsigned short *pc; char buf[BUFSIZE], *src; while (read(STDIN_FILENO, &amp;buf[len], 1) &gt; 0) len++; buf[len] = &#39;\0&#39;; if ((src = malloc(len)) == NULL) { perror(&#34;malloc&#34;); exit(1); } strcpy(src, buf); memset(buf, 0, len); for (pc = (unsigned short *)buf; pos &lt; len; pos++) { switch (src[pos]) { case &#39;&gt;&#39;: pc++; break; case &#39;&lt;&#39;: pc--; break; case &#39;+&#39;: (*pc)++; break; case &#39;-&#39;: (*pc)--; break; case &#39;.&#39;: putchar(*pc); break; case &#39;,&#39;: *pc = getchar(); break; case &#39;[&#39;: if (!(*pc)) { for (opened = 0, pos++; pos &lt; len; pos++) { if (src[pos] == &#39;]&#39; &amp;&amp; !opened) break; else if (src[pos] == &#39;[&#39;) opened++; else if (src[pos] == &#39;]&#39;) opened--; } } break; case &#39;]&#39;: if (*pc) { for (closed = 0, pos--; pos &gt;= 0; pos--) { if (src[pos] == &#39;[&#39; &amp;&amp; !closed) break; else if (src[pos] == &#39;]&#39;) closed++; else if (src[pos] == &#39;[&#39;) closed--; } } break; } } free(src); return (0); } </code></pre>