diff options
85 files changed, 3516 insertions, 604 deletions
diff --git a/.gitattributes b/.gitattributes index 176a458..d76765e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,3 @@ * text=auto + +*.sh text eol=lf diff --git a/.github/workflows/jekyll.yml b/.github/workflows/jekyll.yml new file mode 100644 index 0000000..bd1890d --- /dev/null +++ b/.github/workflows/jekyll.yml @@ -0,0 +1,38 @@ +name: Jekyll + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + env: + JEKYLL_GITHUB_TOKEN: '${{ secrets.GH_TOKEN }}' + name: Deploy + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - name: Build + run: make build + - name: Check integrity + run: | + nohup make serve LIVE_RELOAD=0 & + sleep 5 && make wget + - name: Set up ssh-agent + uses: webfactory/ssh-agent@v0.8.0 + with: + ssh-private-key: '${{ secrets.REMOTE_SSH_KEY }}' + - name: Deploy + run: make deploy + env: + REMOTE_USER: '${{ secrets.REMOTE_USER }}' + REMOTE_HOST: '${{ secrets.REMOTE_HOST }}' + REMOTE_PORT: '${{ secrets.REMOTE_PORT }}' + REMOTE_DIR: '${{ secrets.REMOTE_DIR }}' + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/gh-pages' diff --git a/.github/workflows/makefile-escaping.yml b/.github/workflows/makefile-escaping.yml deleted file mode 100644 index 1e38ff3..0000000 --- a/.github/workflows/makefile-escaping.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Escaping characters in Makefile - -on: - push: - pull_request: - workflow_dispatch: - -jobs: - quoting_arguments: - name: Quoting arguments - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: make test - run: | - cd makefile_escaping - diff <( echo "$expected" ) <( make -f quoting_args.mk test ) - env: - expected: |- - Same - line? - Same line? - Same - line? - Same line? - - escaping_quotes: - name: Escaping quotes - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: make test - run: | - cd makefile_escaping - diff <( echo "$expected" ) <( make -f escaping_quotes.mk test ) - env: - expected: |- - printf '%s\n' 'Includes '\'' quote' - Includes ' quote - bash -c 'printf '\''%s\n'\'' '\''Includes '\''\'\'''\'' quote'\''' - Includes ' quote - bash -c 'bash -c '\''printf '\''\'\'''\''%s\n'\''\'\'''\'' '\''\'\'''\''Includes '\''\'\'''\''\'\''\'\'''\'''\''\'\'''\'' quote'\''\'\'''\'''\''' - Includes ' quote - - shell_output: - name: Shell output - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: "Includes ' quote" - run: | - cd makefile_escaping - diff <( echo "$expected_includes_quote" ) <( mkdir -p -- "Includes ' quote" && cd -- "Includes ' quote" && make -f ../escaping_shell.mk test ) - - name: 'Maybe a comment #' - run: | - cd makefile_escaping - diff <( echo "$expected_maybe_comment" ) <( mkdir -p -- 'Maybe a comment #' && cd -- 'Maybe a comment #' && make -f ../escaping_shell.mk test ) - - name: 'Variable ${reference}' - run: | - cd makefile_escaping - diff <( echo "$expected_variable_reference" ) <( mkdir -p -- 'Variable ${reference}' && cd -- 'Variable ${reference}' && make -f ../escaping_shell.mk test ) - env: - expected_includes_quote: |- - Includes ' quote - Composite value - Simple value - Includes ' quote - expected_maybe_comment: |- - Maybe a comment # - Composite value - Simple value - Maybe a comment # - expected_variable_reference: |- - Variable ${reference} - Composite value - Simple value - Variable ${reference} - - env_variables: - name: Environment variables - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: make test w/ default test_var - run: | - cd makefile_escaping - diff <( echo "$expected_default" ) <( make -f escaping_env_vars.mk test ) - - name: make test w/ tricky test_var - run: | - cd makefile_escaping - diff <( echo "$expected_tricky" ) <( test_var="Quote ' "'and variable ${reference}' make -f escaping_env_vars.mk test ) - - name: make test w/ overridden test_var - run: | - cd makefile_escaping - diff <( echo "$expected_overridden" ) <( make -f escaping_env_vars.mk test test_var="Quote ' "'and variable ${reference}' ) - env: - expected_default: |- - New simple value in test_var - Composite value - New simple value - New simple value in test_var - expected_tricky: |- - Quote ' and variable ${reference} - Composite value - New simple value - Quote ' and variable ${reference} - expected_overridden: |- - Quote ' and variable ${reference} - Composite value - New simple value - Quote ' and variable ${reference} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fdf7591 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/.bundle/ +/.jekyll-cache/ +/_site/ +/.wget/ +/vendor/ diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..bea438e --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.3.1 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..e665814 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,55 @@ +Workspace setup +--------------- + +* To reduce pain, set up [rbenv] to manage your Ruby versions. +Install one that's known to work: + + make ruby + + * Otherwise, make sure you have Ruby and [Bundler] set up. + +* Install dependencies: + + make deps + +* Make sure builds are working: + + make build + +[rbenv]: https://github.com/rbenv/rbenv +[Bundler]: https://bundler.io/ + +Development +----------- + +* Build the website and serve it at http://localhost:4000/blog/: + + make serve + + * It will pick up changes and reload pages automatically. + +Upgrading dependencies +---------------------- + +* To upgrade dependencies in Gemfile.lock & push them to the repository: + + make maintenance + +* Manually upgrade dependencies in Gemfile.lock: + + bundle update + +Building static pages +--------------------- + +If you try to copy the _site directory and open index.html without running the +web server, it won't work: all links will be messed up. +Jekyll doesn't provide native support for generating a static website which can +be browsed without running a web server. + +One workaround is to `wget` the website: + + make serve LIVE_RELOAD=0 # Live reloading breaks wget + make wget + +The truly static version will be downloaded to the .wget/ directory. @@ -0,0 +1,7 @@ +source 'https://rubygems.org' +gem 'jekyll', '~> 4' + +gem 'jekyll-github-metadata' +gem 'jekyll-paginate' +gem 'jekyll-remote-theme' +gem 'jekyll-tidy' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..7a114cc --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,124 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + base64 (0.3.0) + bigdecimal (3.2.3) + colorator (1.1.0) + concurrent-ruby (1.3.5) + csv (3.3.5) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + faraday (2.13.4) + faraday-net_http (>= 2.0, < 3.5) + json + logger + faraday-net_http (3.4.1) + net-http (>= 0.5.0) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-linux-gnu) + forwardable-extended (2.6.0) + google-protobuf (4.32.1-arm64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.32.1-x86_64-linux-gnu) + bigdecimal + rake (>= 13) + htmlbeautifier (1.4.3) + htmlcompressor (0.4.0) + http_parser.rb (0.8.0) + i18n (1.14.7) + concurrent-ruby (~> 1.0) + jekyll (4.4.1) + addressable (~> 2.4) + base64 (~> 0.2) + colorator (~> 1.0) + csv (~> 3.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + json (~> 2.6) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (~> 0.3, >= 0.3.6) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-github-metadata (2.16.1) + jekyll (>= 3.4, < 5.0) + octokit (>= 4, < 7, != 4.4.0) + jekyll-paginate (1.1.0) + jekyll-remote-theme (0.4.3) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) + jekyll-sass-converter (3.0.0) + sass-embedded (~> 1.54) + jekyll-tidy (0.2.2) + htmlbeautifier + htmlcompressor + jekyll + jekyll-watch (2.2.1) + listen (~> 3.0) + json (2.14.1) + kramdown (2.5.1) + rexml (>= 3.3.9) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + logger (1.7.0) + mercenary (0.4.0) + net-http (0.6.0) + uri + octokit (6.1.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (6.0.2) + rake (13.3.0) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.4.4) + rouge (4.6.0) + rubyzip (2.4.1) + safe_yaml (1.0.5) + sass-embedded (1.92.1-arm64-darwin) + google-protobuf (~> 4.31) + sass-embedded (1.92.1-x86_64-linux-gnu) + google-protobuf (~> 4.31) + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + unicode-display_width (2.6.0) + uri (1.0.3) + webrick (1.9.1) + +PLATFORMS + arm64-darwin-21 + arm64-darwin-22 + x86_64-linux + +DEPENDENCIES + jekyll (~> 4) + jekyll-github-metadata + jekyll-paginate + jekyll-remote-theme + jekyll-tidy + +BUNDLED WITH + 2.5.9 diff --git a/LICENSE.txt b/LICENSE.txt index 62c44fd..c9d72a5 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2015 Egor Tensin <Egor.Tensin@gmail.com> +Copyright (c) 2015 Egor Tensin <egor@tensin.name> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a833820 --- /dev/null +++ b/Makefile @@ -0,0 +1,73 @@ +include prelude.mk + +LIVE_RELOAD ?= 1 +$(eval $(call noexpand,LIVE_RELOAD)) + +.PHONY: all +all: serve + +.PHONY: ruby +ruby: + if command -v rbenv &> /dev/null; then rbenv install --skip-existing; fi + +.PHONY: deps +deps: ruby + bundle install + +.PHONY: maintenance +maintenance: ruby + bundle config set frozen false + bundle update + + @git_status="$$( git status --porcelain=v1 )" && \ + if [ -z "$$git_status" ]; then \ + true; \ + elif [ "$$git_status" = ' M Gemfile.lock' ]; then \ + git commit -am 'bump dependencies' && \ + git push -q; \ + else \ + echo; \ + echo '-----------------------------------------------------------------'; \ + echo 'Error: unrecognized modifications in the repository:'; \ + echo "$$git_status"; \ + echo '-----------------------------------------------------------------'; \ + exit 1; \ + fi + +jekyll := bundle exec jekyll + +.PHONY: build +build: + $(jekyll) build + +.PHONY: serve +serve: +ifeq ($(LIVE_RELOAD),1) + $(jekyll) serve --livereload +else + $(jekyll) serve +endif + +URL := http://localhost:4000/blog/ + +.PHONY: wget +wget: + wget --no-verbose --recursive --no-parent --convert-links --adjust-extension -e robots=off --directory-prefix=.wget -- '$(call escape,$(URL))' + +.PHONY: view +view: + xdg-open '$(call escape,$(URL))' &> /dev/null + +REMOTE_USER ?= who +REMOTE_HOST ?= where +REMOTE_PORT ?= 22 +REMOTE_DIR ?= /path/to/dir + +$(eval $(call noexpand,REMOTE_USER)) +$(eval $(call noexpand,REMOTE_HOST)) +$(eval $(call noexpand,REMOTE_PORT)) +$(eval $(call noexpand,REMOTE_DIR)) + +.PHONY: deploy +deploy: + rsync -avh -e 'ssh -p $(call escape,$(REMOTE_PORT)) -o StrictHostKeyChecking=no' _site/ '$(call escape,$(REMOTE_USER)@$(REMOTE_HOST):$(REMOTE_DIR)/)' --delete @@ -1,7 +1,22 @@ My blog ======= -My blog, hosted at https://egor-tensin.github.io/blog/. +[](https://github.com/egor-tensin/blog/actions/workflows/jekyll.yml) + +My blog, hosted at https://tensin.name/blog/. + +Development +----------- + +This is a static website, built using [Jekyll]. +[jekyll-theme] is used as the Jekyll theme. + +[Jekyll]: https://jekyllrb.com/ +[jekyll-theme]: https://github.com/egor-tensin/jekyll-theme + +See [DEVELOPMENT.md] for details. + +[DEVELOPMENT.md]: DEVELOPMENT.md License ------- diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..848d9bb --- /dev/null +++ b/_config.yml @@ -0,0 +1,77 @@ +# Jekyll +baseurl: /blog +host: 0.0.0.0 +open_url: true +show_drafts: true +strict_front_matter: true + +exclude: + - .wget/ + - DEVELOPMENT.md + - Makefile + - prelude.mk + - README.md + +collections: + notes: + output: true + # I want the "natural" sorting (a.k.a. whatever the sort_natural filter does). + #sort_by: title + +defaults: + - scope: + path: '' + type: pages + values: + layout: page + navbar_priority: 999 + - scope: + path: '' + type: posts + values: + layout: post + - scope: + path: '' + type: notes + values: + layout: page + +excerpt_separator: '' +permalink: /:title:output_ext + +highlighter: rouge +markdown: kramdown +kramdown: + syntax_highlighter_opts: + span: + disable: true + +# Plugins +plugins: + - jekyll-github-metadata + - jekyll-paginate + - jekyll-remote-theme + - jekyll-tidy + +# jekyll-github-metadata +repository: egor-tensin/blog +# jekyll-paginate +paginate: 100 +# jekyll-remote-theme +remote_theme: egor-tensin/jekyll-theme@e000b4d38754517f3609443ed6314dad3f0a3b5d + +# Theme settings +settings: + project: + name: Blog + author: + name: Egor Tensin + email: egor@tensin.name + sidebar: + latest_posts: + hide: true + links: + - {rel: apple-touch-icon, sizes: 180x180, href: /assets/favicon/apple-touch-icon.png} + - {rel: icon, type: image/png, sizes: 32x32, href: /assets/favicon/favicon-32x32.png} + - {rel: icon, type: image/png, sizes: 16x16, href: /assets/favicon/favicon-16x16.png} + - {rel: manifest, href: /assets/favicon/site.webmanifest} diff --git a/_drafts/.gitkeep b/_drafts/.gitkeep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/_drafts/.gitkeep diff --git a/_includes/custom-sidebar.html b/_includes/custom-sidebar.html new file mode 100644 index 0000000..0aad3ac --- /dev/null +++ b/_includes/custom-sidebar.html @@ -0,0 +1,8 @@ +{% unless page.sidebar.notes.hide %} + +{% capture notes %} +{% include notes.html sidebar=true %} +{% endcapture %} +{% include jekyll-theme/sidebar/entry.html content=notes %} + +{% endunless %} diff --git a/_includes/notes.html b/_includes/notes.html new file mode 100644 index 0000000..ca91e54 --- /dev/null +++ b/_includes/notes.html @@ -0,0 +1,14 @@ +{% assign note_list = site.notes | sort_natural: 'title' %} +<div class="list-group wide-enough"> + <div class="list-group-item disabled"> + <h4 class="list-group-item-heading">notes</h4> + </div> + {% for note in note_list %} + <a href="{{ note.url | relative_url }}" class="list-group-item"> + <h5 class="list-group-item-heading flex-header"> + <span>{{ note.title }}</span> + <small class="flex-header-right"><span class="glyphicon glyphicon-menu-right"></span> {{ note.subtitle }}</small> + </h5> + </a> + {% endfor %} +</div> diff --git a/_notes/bash.html b/_notes/bash.html new file mode 100644 index 0000000..c401522 --- /dev/null +++ b/_notes/bash.html @@ -0,0 +1,184 @@ +--- +title: bash +subtitle: best practices +layout: nosidebar +links: + - {rel: stylesheet, href: 'assets/css/guides.css'} +features: + - title: Script header + topics: + - do: + - | + #!/usr/bin/env bash + + set -o errexit -o nounset -o pipefail + shopt -s inherit_errexit lastpipe + dont: + - | + #!/bin/sh -e + - title: Arrays + topics: + - title: Declaration + do: + - | + local -a xs=() + declare -a xs=() + local -A xs=() + declare -A xs=() + dont: + - | + local -a xs + declare -a xs + local -A xs + declare -A xs + + # Doesn't work with nounset: + echo "${#xs[@]}" + - title: Expansion + do: + - | + func ${arr[@]+"${arr[@]}"} + dont: + - | + # Doesn't work with nounset: + func "${arr[@]}" + - | + # Expands to 0 arguments instead of 1: + declare -a arr=('') + func "${arr[@]+"${arr[@]}"}" + - title: unset + do: + - | + unset -v 'arr[x]' + unset -v 'arr[$i]' + dont: + - | + # May break due to globbing: + unset -v arr[x] + # In addition, possible quoting problem: + unset -v arr[$i] + # Doesn't work for some reason: + unset -v 'arr["x"]' + unset -v 'arr["]"]' + # Also rejected: + unset -v 'arr["$i"]' + + # An insightful discussion on the topic: + # https://lists.gnu.org/archive/html/help-bash/2016-09/msg00020.html + - title: errexit + topics: + - title: Command substitution + do: + - | + shopt -s inherit_errexit + + foo() { echo foo ; } + bar() { false ; echo bar >&2 ; } + + output="$( bar )" + foo "$output" + + # If inherit_errexit is unavailable, you can do + #output="$( set -e; bar )" + dont: + - | + foo() { echo foo ; } + bar() { false ; echo bar >&2 ; } + + # This will print both "foo" and "bar": + foo "$( bar )" + # This will also print "foo": + foo "$( false )" + - | + foo() { echo foo ; } + bar() { false ; echo bar >&2 ; } + + # This will still print both "foo" and "bar". + output="$( bar )" + foo "$output" + + # This won't print anything. + output="$( false )" + foo "$output" + - title: Process substitution + do: + - | + shopt -s lastpipe + + result=() + cmd | while IFS= read -r line; do + result+=("$( process_line "$line" )") + done + dont: + - | + # Without lastpipe, the loop is executed is a subshell, + # and the array will be empty: + result=() + cmd | while IFS= read -r line; do + result+=("$( process_line "$line" )") + done + - | + # errexit doesn't work for <( cmd ) no matter what: + while IFS= read -r line; do + process_line "$line" + done < <( cmd ) + # This will be printed even if cmd fails: + echo 'should never see this' + - | + # This breaks if $output contains the \0 byte: + output="$( cmd )" + + while IFS= read -r line; do + process_line "$line" + done <<< "$output" + - title: Functions + do: + - | + foo() { false ; echo foo >&2 ; } + + foo + echo ok + dont: + - | + foo() { false ; echo foo >&2 ; } + + # This will print "foo" no matter what. + if foo; then + echo ok + fi + + # Same below. + foo && echo ok + foo || echo fail + + # It currently appears to be completely impossible to + # execute a function inside a conditional with errexit + # enabled. Therefore, you should try to avoid this + # whenever possible. +--- +{% for feature in page.features %} + <h2>{{ feature.title }}</h2> + {% for topic in feature.topics %} + {% if topic.title %} + <h3>{{ topic.title }}</h3> + {% endif %} + <div class="row"> + <div class="col-md-6"> + {% for guide in topic.do %} + <div class="pre_container pre_do"> + {% highlight bash %}{{ guide }}{% endhighlight %} + <div class="pre_mark"><span class="glyphicon glyphicon-ok"></span></div> + </div> + {% endfor %} + </div> + <div class="col-md-6"> + {% for guide in topic.dont %} + <div class="pre_container pre_dont"> + {% highlight bash %}{{ guide }}{% endhighlight %} + <div class="pre_mark"><span class="glyphicon glyphicon-remove"></span></div> + </div> + {% endfor %} + </div> + </div> + {% endfor %} +{% endfor %} diff --git a/_notes/gdb.md b/_notes/gdb.md new file mode 100644 index 0000000..47db5c9 --- /dev/null +++ b/_notes/gdb.md @@ -0,0 +1,135 @@ +--- +title: gdb +subtitle: cheat sheet +links: + - {rel: stylesheet, href: 'assets/css/gdb.css'} +--- +Core dumps +---------- + +* Where are my core dumps? + + cat /proc/sys/kernel/core_pattern + +* Put core dumps in a directory: + + mkdir /coredumps + chmod 0777 /coredumps + echo '/coredumps/core.%e.%p' | tee /proc/sys/kernel/core_pattern + +* Still no dumps :-( + + ulimit -c unlimited + +* If dumps are piped to systemd-coredump, you can examine them using +`coredumpctl`. + + <div markdown="1" class="table-responsive"> + | List dumps | `coredumpctl` + | Debug the last dump | `coredumpctl gdb` + | Extract the last dump | `coredumpctl dump -o core` + {: .table .table-bordered } + </div> + +.gdbinit +-------- + + # Without these, gdb is hardly usable: + set pagination off + set confirm off + set print pretty on + + # Save history: + set history save on + set history filename ~/.gdb-history + set history size 10000 + +Basics +------ + +<div markdown="1" class="table-responsive"> + +| Run | `r` +| Continue | `c` +| Breakpoint at function | `b FUNC` +| Breakpoint at address | `b *0xdeadbeef` +| List breakpoints | `i b` +| Disable breakpoint | `dis N` +| Enable breakpoint | `en N` +| Delete breakpoint | `d N` +| Call stack | `bt` +| Call stack: all threads | `thread apply all bt` +| Go to frame | `f N` +| Switch to thread | `t N` +| Disassemble | `disas FUNC` +| Step over line | `n` +| Step over instruction | `si` +| Step out of frame | `fin` +{: .table .table-bordered } + +</div> + +Hint: put this in your ~/.gdbinit and use `bta` as a shortcut: + + define bta + thread apply all backtrace + end + +Data inspection +--------------- + +<div markdown="1" class="table-responsive"> + +| Disassemble 5 instructions | `x/5i 0xdeadbeef` +| Print a 64-bit address | `x/1xg 0xdeadbeef` +| Print a 32-bit address | `x/1xw 0xdeadbeef` +| Print anything | `p sa->__sigaction_handler.sa_handler` +| Describe a type | `ptype struct sigaction` +| Describe a type with offsets | `ptype /o struct sigaction` +| Disassemble all code sections | `objdump -d /proc/self/exe` +| Disassemble a single section | `objdump -d -j .init /proc/self/exe` +| Display the section contents | `objdump -s -j .data /proc/self/exe` +{: .table .table-bordered } + +</div> + +Hint: put this in your ~/.gdbinit: + + define xxd + dump binary memory /tmp/dump.bin $arg0 ((char *)$arg0)+$arg1 + shell xxd -groupsize 1 /tmp/dump.bin + shell rm -f /tmp/dump.bin + end + +You can then use `xxd ADDR LEN` to display, in my opinion, the best formatting +for memory dumps: + + (gdb) xxd main 24 + 00000000: f3 0f 1e fa 41 57 41 89 ff bf 05 00 00 00 41 56 ....AWA.......AV + 00000010: 49 89 f6 41 55 41 54 55 I..AUATU + +Debuginfod +---------- + +If your distribution provides a Debuginfod server, use it! +For example, see [Arch], [Debian], [Fedora]. +In ~/.gdbinit, add + + set debuginfod enabled on + +[Arch]: https://wiki.archlinux.org/title/Debuginfod +[Debian]: https://wiki.debian.org/Debuginfod +[Fedora]: https://fedoraproject.org/wiki/Debuginfod + + +Intel syntax +------------ + +This is just me being a baby duck. +In ~/.gdbinit: + + set disassembly-flavor intel + +With `objdump`: + + objdump -Mintel -d /proc/self/exe diff --git a/_notes/latex.md b/_notes/latex.md new file mode 100644 index 0000000..35e4a7d --- /dev/null +++ b/_notes/latex.md @@ -0,0 +1,38 @@ +--- +title: LaTeX +subtitle: document template +--- +A more-or-less complete, but still very basic LaTeX document follows. + +```tex +\documentclass[11pt]{article} + +% Basic setup +\usepackage{cmap} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} % Use T2A for non-ASCII scripts +\usepackage[english]{babel} + +% Completely arbitrary settings follow: + +% Sans serif font by default +\renewcommand\familydefault{\sfdefault} + +% Document margins +\usepackage[margin=2.5cm]{geometry} + +% Paragraph indents +\usepackage{parskip} +\setlength\parindent{0cm} +\setlength\parskip{0cm} + +% URLs +\usepackage[colorlinks=true,urlcolor=blue]{hyperref} + +\begin{document} + +Hello, \LaTeX! +Repository link: \href{https://github.com/egor-tensin/blog}{https://github.com/egor-tensin/blog}. + +\end{document} +``` diff --git a/_notes/makefile.md b/_notes/makefile.md new file mode 100644 index 0000000..62e0570 --- /dev/null +++ b/_notes/makefile.md @@ -0,0 +1,129 @@ +--- +title: make +subtitle: best practices +layout: nosidebar +links: + - {rel: stylesheet, href: 'assets/css/guides.css'} +features: + - note: This should go on top of every Makefile. + sections: + - do: + - | + MAKEFLAGS += --no-builtin-rules --no-builtin-variables --warn-undefined-variables + unexport MAKEFLAGS + .DEFAULT_GOAL := all + .DELETE_ON_ERROR: + .SUFFIXES: + SHELL := bash + .SHELLFLAGS := -eu -o pipefail -c + + escape = $(subst ','\'',$(1)) + + define noexpand + ifeq ($$(origin $(1)),environment) + $(1) := $$(value $(1)) + endif + ifeq ($$(origin $(1)),environment override) + $(1) := $$(value $(1)) + endif + ifeq ($$(origin $(1)),command line) + override $(1) := $$(value $(1)) + endif + endef + - note: Quote command arguments and use the `escape` function on variables and shell output. + sections: + - do: + - | + var := Includes ' quote + test: + printf '%s\n' '$(call escape,$(var))' + dont: + - | + var := Includes space + test: + printf '%s\n' $(var) + - | + var := Includes ' quote + test: + printf '%s\n' '$(var)' + - do: + - | + cwd := $(shell pwd) + test: + printf 'In directory %s\n' '$(call escape,$(cwd))' + dont: + - | + cwd := $(shell pwd) + test: + printf 'In directory %s\n' $(cwd) + - | + cwd := $(shell pwd) + test: + printf 'In directory %s\n' '$(cwd)' + - note: Use the `noexpand` function on environment variables or variables that can be overridden on the command line. + sections: + - do: + - | + has_default ?= Default value + $(eval $(call noexpand,has_default)) + + test: + echo '$(call escape,$(has_default))' + dont: + - | + has_default ?= Default value + + test: + echo '$(call escape,$(has_default))' + - | + has_default ?= Default value + export has_default + + test: + echo "$$has_default" + - do: + - | + $(eval $(call noexpand,ENV_VAR)) + + test: + echo '$(call escape,$(ENV_VAR))' + dont: + - | + test: + echo '$(call escape,$(ENV_VAR))' +--- +I've made a [detailed blog post] about how all of this works. +{: .alert .alert-info } + +[detailed blog post]: {% post_url 2020-05-20-makefile-escaping %} + +{% for feature in page.features %} + {{ feature.note | markdownify }} + {% for section in feature.sections %} +<div class="row"> + {% if section.do %} + {% if section.dont %}{% assign width = "6" %}{% else %}{% assign width = "12" %}{% endif %} + <div class="col-md-{{ width }}"> + {% for guide in section.do %} + <div class="pre_container pre_do"> + <pre>{{ guide }}</pre> + <div class="pre_mark"><span class="glyphicon glyphicon-ok"></span></div> + </div> + {% endfor %} + </div> + {% endif %} + {% if section.dont %} + {% if section.do %}{% assign width = "6" %}{% else %}{% assign width = "12" %}{% endif %} + <div class="col-md-{{ width }}"> + {% for guide in section.dont %} + <div class="pre_container pre_dont"> + <pre>{{ guide }}</pre> + <div class="pre_mark"><span class="glyphicon glyphicon-remove"></span></div> + </div> + {% endfor %} + </div> + {% endif %} +</div> + {% endfor %} + <hr/> +{% endfor %} diff --git a/_notes/markdown.md b/_notes/markdown.md new file mode 100644 index 0000000..39cbbd9 --- /dev/null +++ b/_notes/markdown.md @@ -0,0 +1,44 @@ +--- +title: Markdown +subtitle: style guide +--- +* `diff`- and HTML-friendliness is valued over human-readability. +* Every sentence starts on a new line ("semantic newlines"). +* Lines are at most 79 characters long, not counting neither the carriage +return, nor the line feed characters. + * Not 80 characters, because when you display a 80-character line with a +line feed at the end in Windows' `cmd`, an extra empty line is added. +* No hanging indents in lists. + * Nested lists are indented with 4 spaces. + * No hanging indents in those also. + * Longer items wrap at 79 characters and continue from the leftmost +character column. +Additional sentences start there also. +* Prefer reference-style links over inline links. +Omit the second pair of brackets `[]` entirely where appropriate. +For example, [Google] is preferred over both [Google](https://ya.ru) and +[I'm feeling lucky][google] (see [this document's source]). +* First- and second-level headers are underlined with strings of `=` and `-`. +The number of `=`/`-` signs must be equal to the number of characters in the +header. +* File paths are enclosed in double quotes. +Environment variable names are enclosed in a pair of backticks (\`) unless it's +a part of a path. +Executable names are enclosed in a pair of backticks (\`) unless it's a part of +a path, a link or a header. +* Code blocks are indented with 4 spaces. + + Code blocks inside lists are indented according to the spec + (https://github.github.com/gfm/#list-items), i.e. the column of the first + non-whitespace character in the item + 4. + +* Don't mix fenced code blocks with indented code blocks in a single document. + +| In a table, | the first | row | is underlined. +| ----------- | --------- | ----- | -------------- +| Leftmost | vertical | lines | are required. +| Rightmost | vertical | lines | are omitted. +{: .table .table-bordered } + +[Google]: https://www.google.com/ +[this document's source]: https://raw.githubusercontent.com/{{ site.github.repository_nwo }}/gh-pages/{{ page.path }} diff --git a/_posts/2015-07-03-std-call-once-bug.md b/_posts/2015-07-03-std-call-once-bug.md new file mode 100644 index 0000000..8191ad3 --- /dev/null +++ b/_posts/2015-07-03-std-call-once-bug.md @@ -0,0 +1,252 @@ +--- +title: std::call_once bug in Visual C++ 2012/2013 +category: C++ +--- +I've recently come across a nasty standard library bug in the implementation +shipped with Microsoft Visual Studio 2012/2013. +[StackOverflow was of no help], so I had to somehow report the bug to the +maintainers. +Oddly enough, Visual Studio's [Connect page] wouldn't let me report one, +complaining about the lack of permissions, even though I was logged in from my +work account, associated with my Visual Studio 2013 installation. + +Fortunately, I've come across the personal website of this amazing guy, +[Stephan T. Lavavej], who appears to be the chief maintainer of Microsoft's +standard library implementation. +He seems to be your go-to guy when it comes to obvious standard library +misbehaviours. + +[StackOverflow was of no help]: https://stackoverflow.com/questions/26477070/concurrent-stdcall-once-calls +[Connect page]: https://connect.microsoft.com/VisualStudio +[Stephan T. Lavavej]: http://nuwen.net/stl.html + +C++11 and singletons +-------------------- + +Anyway, the story begins with me trying to implement the singleton pattern +using C++11 facilities like this: + +```c++ +#include <mutex> + +template <typename Derived> +class Singleton { +public: + static Derived& get_instance() { + std::call_once(initialized_flag, &initialize_instance); + return Derived::get_instance_unsafe(); + } + +protected: + Singleton() = default; + ~Singleton() = default; + + static Derived& get_instance_unsafe() { + static Derived instance; + return instance; + } + +private: + static void initialize_instance() { + Derived::get_instance_unsafe(); + } + + static std::once_flag initialized_flag; + + Singleton(const Singleton&) = delete; + Singleton& operator=(const Singleton&) = delete; +}; + +template <typename Derived> +std::once_flag Singleton<Derived>::initialized_flag; +``` + +Neat, huh? +Now other classes can inherit from `Singleton`, implementing the singleton +pattern effortlessly: + +```c++ +class Logger : public Singleton<Logger> { +private: + Logger() = default; + ~Logger() = default; + + friend class Singleton<Logger>; +}; +``` + +Note that the [N2660] standard proposal isn't/wasn't implemented in the +compilers shipped with Visual Studio 2012/2013. +If it was, I wouldn't, of course, need to employ this `std::call_once` +trickery, and the implementation would be much simpler, i.e. something like +this: + +```c++ +class Logger { +public: + static Logger& get_instance() { + static Logger instance; + return instance; + } + +private: + Logger() = default; + ~Logger() = default; +}; +``` + +<div class="alert alert-info" markdown="1"> + +The point is that the `Logger::get_instance` routine above wasn't thread-safe +until C++11. +Imagine what might happen if `Logger`'s constructor takes some time to +initialize the instance. +If a couple of threads then call `get_instance`, the first thread might begin +the initialization process, making the other thread believe that the instance +had already been intialized. +This other thread might then return a reference to the instance which hasn't +yet completed its initialization and is most likely unsafe to use. + +Since C++11 includes the proposal mentioned above, this routine would indeed be +thread-safe in C++11. +Unfortunately, the compilers shipped with Visual Studio 2012/2013 don't/didn't +implement this particular proposal, which caused me to look at +`std::call_once`, which seemed to implement exactly what I needed. + +</div> + +[N2660]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm + +Problem +------- + +Unfortunately, matters became a bit more complicated when I tried to introduce +two singletons, one having a dependency on the other. +I had `Logger`, like in the example above, and some kind of a "master" +singleton (let's call it `Duke`). +`Duke`'s constructor was complicated and time-consuming, and definitely +required some logging to be done. +I thought that I could simply call `Logger::get_instance` inside `Duke`'s +constructor, and everything looked fine at first glance. + +```c++ +#include <chrono> +#include <thread> + +class Logger : public Singleton<Logger> { +public: + Logger& operator<<(const char* msg) { + // Actual logging is stripped for brevity. + return *this; + } + +private: + Logger() { + // Opening log files, etc. + std::this_thread::sleep_for(std::chrono::seconds{3}); + } + + ~Logger() = default; + + friend class Singleton<Logger>; +}; + +class Duke : public Singleton<Duke> { +private: + Duke() { + Logger::get_instance() << "started Duke's initialization"; + // It's a lot of work to be done. + std::this_thread::sleep_for(std::chrono::seconds{10}); + Logger::get_instance() << "finishing Duke's initialization"; + } + + ~Duke() = default; + + friend class Singleton<Duke>; +}; +``` + +Now, what happens if I have two threads, one using the `Duke` instance, and the +other logging something? +Like in this example: + +```c++ +#include <thread> + +namespace { + +void get_logger() { + entered(__FUNCTION__); + Logger::get_instance(); + exiting(__FUNCTION__); +} + +void get_duke() { + entered(__FUNCTION__); + Duke::get_instance(); + exiting(__FUNCTION__); +} + +} + +int main() { + std::thread t1{&get_duke}; + std::thread t2{&get_logger}; + t1.join(); + t2.join(); + return 0; +} +``` + +`entered` and `exiting` are utility functions to print timestamps. +The implementation is included in the [complete code sample]. +{: .alert .alert-info } + +The first thread is supposed to have the total running time of about 13 +seconds, right? +Three seconds to initialize the `Logger` instance and ten to initialize the +`Duke` instance. +The second thread, similarly, is supposed to be done in about 3 seconds +required for the initialization of `Logger`. + +Weirdly, this program produces the following output when compiled using Visual +Studio 2013's compiler: + + Entered `anonymous-namespace'::get_duke at Fri Jul 03 02:26:16 2015 + Entered `anonymous-namespace'::get_logger at Fri Jul 03 02:26:16 2015 + Exiting `anonymous-namespace'::get_duke at Fri Jul 03 02:26:29 2015 + Exiting `anonymous-namespace'::get_logger at Fri Jul 03 02:26:29 2015 + +Isn't it wrong that the second thread actually took the same 13 seconds as the +first thread? +Better check with some other compiler in case it was me who made a mistake. +Unfortunately, the program behaves as expected when compiled using GCC: + + Entered get_logger at Fri Jul 3 02:27:12 2015 + Entered get_duke at Fri Jul 3 02:27:12 2015 + Exiting get_logger at Fri Jul 3 02:27:15 2015 + Exiting get_duke at Fri Jul 3 02:27:25 2015 + +So it appears that the implementation of `std::call_once` shipped with Visual +Studio 2012/2013 relies on some kind of a global lock, which causes even the +simple example above to misbehave. + +The [complete code sample] to demonstrate the misbehaviour described above can +be found in this blog's repository. + +[complete code sample]: {{ site.github.repository_url }}/tree/master/std_call_once_bug + +Conclusion +---------- + +So, since I couldn't submit the bug via Visual Studio's [Connect page], I wrote +to Mr. Lavavej directly, not hoping for an answer. +Amazingly, it took him less than a day to reply. +He told me he was planning to overhaul `std::call_once` for Visual Studio 2015. +Meanwhile, I had to stick to something else; I think I either dropped logging +from `Duke`'s constructor or initialized all the singleton instances manually +before actually using any of them. +In a few months, Mr. Lavavej replied to me that the bug has been fixed in +Visual Studio 2015 RTM. +I would like to thank him for the professionalism and responsibility he's +shown. diff --git a/_posts/2017-01-07-building-boost.md b/_posts/2017-01-07-building-boost.md new file mode 100644 index 0000000..cddba13 --- /dev/null +++ b/_posts/2017-01-07-building-boost.md @@ -0,0 +1,265 @@ +--- +title: Building Boost on Windows +category: C++ +--- +Below you can find the steps required to build Boost libraries on Windows. +These steps tightly fit my typical workflow, which is to use Boost libraries in +CMake builds using either Visual Studio or the combination of Cygwin + +MinGW-w64. +I would expect, however, that the procedure for the latter toolset can easily +be adjusted for generic GCC distributions (including vanilla GCCs found in +popular Linux distributions). + +One of the features of this workflow is that I build throwaway, "run +everywhere, record the results, and scrap it" executables more often than not, +so I prefer to link everything statically, including, for instance, C/C++ +runtimes. +This is implemented by passing `runtime-link=static` to Boost's build utility +`b2`; change this to `runtime-link=dynamic` to link the runtime dynamically. + +Excerpts from shell sessions in this post feature a few different commands +besides Boost's `b2` and `cmake`, like `cd` and `cat`. +They are used to hint at my personal directory layout, display various +auxiliary files, etc. +Windows' `cd`, for example, simply prints the current working directory; +Cygwin's `pwd` serves the same purpose. +`cat` is used to display files. + +Visual Studio +------------- + +Statically-linked Boost libraries are built, both the debug and the release +versions of them (these are default settings). +While it is required to keep x86 and x64 libraries in different directories (to +avoid file name clashes), it's not necessary to separate debug libraries from +their release counterparts, because that information is actually encoded in +file names (the "gd" suffix). + +### x86 + +{% capture out1 %} +D:\workspace\third-party\boost_1_61_0\msvc +{% endcapture %} + +{% capture cmd3 %} +b2 --stagedir=stage\x86 ^ + runtime-link=static ^ + --with-filesystem ^ + --with-program_options ^ + ... +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cd' out=out1 %} +{% include jekyll-theme/shell.html cmd='bootstrap' %} +{% include jekyll-theme/shell.html cmd=cmd3 %} + +### x64 + +The only important difference is that you have to pass `address-model=64` to +`b2` (notice also the different "staging" directory). + +{% capture out1 %} +D:\workspace\third-party\boost_1_61_0\msvc +{% endcapture %} + +{% capture cmd3 %} +b2 --stagedir=stage\x64 ^ + runtime-link=static ^ + address-model=64 ^ + --with-filesystem ^ + --with-program_options ^ + ... +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cd' out=out1 %} +{% include jekyll-theme/shell.html cmd='bootstrap' %} +{% include jekyll-theme/shell.html cmd=cmd3 %} + +Cygwin + MinGW-w64 +------------------ + +Contrary to the Visual Studio example above, it is required to store debug and +release libraries *as well as* x86 and x64 libraries in different directories. +It is required to avoid file name clashes; unlike the Visual Studio "toolset" +(in Boost's terms), GCC-derived toolsets don't encode any information (like +whether the debug or the release version of a library was built) in file names. + +Also, linking the runtime statically doesn't really make sense for MinGW, as it +always links to msvcrt.dll, which is [simply the Visual Studio 6.0 runtime]. + +[simply the Visual Studio 6.0 runtime]: https://sourceforge.net/p/mingw-w64/wiki2/The%20case%20against%20msvcrt.dll/ + +In the examples below, only the debug versions of the libraries are built. +Build the release versions by executing the same command, and substituting +`variant=release` instead of `variant=debug` and either +`--stagedir=stage/x86/release` or `--stagedir=stage/x64/release`, depending +on the target architecture. + +### x86 + +{% capture out1 %} +/cygdrive/d/workspace/third-party/boost_1_61_0/mingw +{% endcapture %} + +{% capture out3 %} +using gcc : : i686-w64-mingw32-g++ ; +{% endcapture %} + +{% capture cmd4 %} +./b2 toolset=gcc-mingw \ + target-os=windows \ + link=static \ + variant=debug \ + --stagedir=stage/x86/debug \ + --user-config=user-config-x86.jam \ + --with-filesystem \ + --with-program_options \ + ... +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='pwd' out=out1 %} +{% include jekyll-theme/shell.html cmd='./bootstrap.sh' %} +{% include jekyll-theme/shell.html cmd='cat user-config-x86.jam' out=out3 %} +{% include jekyll-theme/shell.html cmd=cmd4 %} + +The "user" configuration file above stopped working at some point; not sure as +to who's to blame, Cygwin or Boost. +If you see something like "`error: provided command 'i686-w64-mingw32-g++' not +found`", add ".exe" to the binary name above, so that the whole file reads +"`using gcc : : i686-w64-mingw32-g++.exe ;`". +{: .alert .alert-info } + +### x64 + +Notice the two major differences from the x86 example: + +* the addition of `address-model=64` (as in the example for Visual Studio), +* the different "user" configuration file, pointing to `x86_64-w64-mingw32-g++` +instead of `i686-w64-mingw32-g++`. + +Again, as in the example for Visual Studio, a different "staging" directory +needs to be specified using the `--stagedir` parameter. + +{% capture out1 %} +/cygdrive/d/workspace/third-party/boost_1_61_0/mingw +{% endcapture %} + +{% capture out3 %} +using gcc : : x86_64-w64-mingw32-g++ ; +{% endcapture %} + +{% capture cmd4 %} +./b2 toolset=gcc-mingw \ + address-model=64 \ + target-os=windows \ + link=static \ + variant=debug \ + --stagedir=stage/x64/debug \ + --user-config=user-config-x64.jam \ + --with-filesystem \ + --with-program_options \ + ... +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='pwd' out=out1 %} +{% include jekyll-theme/shell.html cmd='./bootstrap.sh' %} +{% include jekyll-theme/shell.html cmd='cat user-config-x64.jam' out=out3 %} +{% include jekyll-theme/shell.html cmd=cmd4 %} + +The "user" configuration file above stopped working at some point; not sure as +to who's to blame, Cygwin or Boost. +If you see something like "`error: provided command 'x86_64-w64-mingw32-g++' +not found`", add ".exe" to the binary name above, so that the whole file reads +"`using gcc : : x86_64-w64-mingw32-g++.exe ;`". +{: .alert .alert-info } + +Usage in CMake +-------------- + +### Visual Studio + +Examples below apply to Visual Studio 2015. +You may want to adjust the paths. + +#### x86 + +{% capture out1 %} +D:\workspace\build\test_project\msvc\x86 +{% endcapture %} + +{% capture cmd2 %} +cmake -G "Visual Studio 14 2015" ^ + -D BOOST_ROOT=D:\workspace\third-party\boost_1_61_0\msvc ^ + -D BOOST_LIBRARYDIR=D:\workspace\third-party\boost_1_61_0\msvc\stage\x86\lib ^ + -D Boost_USE_STATIC_LIBS=ON ^ + -D Boost_USE_STATIC_RUNTIME=ON ^ + ... +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cd' out=out1 %} +{% include jekyll-theme/shell.html cmd=cmd2 %} + +#### x64 + +{% capture out1 %} +D:\workspace\build\test_project\msvc\x64 +{% endcapture %} + +{% capture cmd2 %} +cmake -G "Visual Studio 14 2015 Win64" ^ + -D BOOST_ROOT=D:\workspace\third-party\boost_1_61_0\msvc ^ + -D BOOST_LIBRARYDIR=D:\workspace\third-party\boost_1_61_0\msvc\stage\x64\lib ^ + -D Boost_USE_STATIC_LIBS=ON ^ + -D Boost_USE_STATIC_RUNTIME=ON ^ + ... +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cd' out=out1 %} +{% include jekyll-theme/shell.html cmd=cmd2 %} + +### Cygwin & MinGW-w64 + +Examples below only apply to debug CMake builds. +Notice that, contrary to the Visual Studio examples above, debug and release +builds must be kept in separate directories. +You may also want to adjust the paths. + +#### x86 + +{% capture out1 %} +/cygdrive/d/workspace/build/test_project/mingw/x86/debug +{% endcapture %} + +{% capture cmd2 %} +cmake -G "Unix Makefiles" \ + -D CMAKE_BUILD_TYPE=Debug \ + -D CMAKE_C_COMPILER=i686-w64-mingw32-gcc \ + -D CMAKE_CXX_COMPILER=i686-w64-mingw32-g++ \ + -D BOOST_ROOT=/cygdrive/d/workspace/third-party/boost_1_61_0/mingw \ + -D BOOST_LIBRARYDIR=/cygdrive/d/workspace/third-party/boost_1_61_0/mingw/stage/x86/debug/lib \ + -D Boost_USE_STATIC_LIBS=ON \ + ... +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='pwd' out=out1 %} +{% include jekyll-theme/shell.html cmd=cmd2 %} + +#### x64 + +{% capture out1 %} +/cygdrive/d/workspace/build/test_project/mingw/x64/debug +{% endcapture %} + +{% capture cmd2 %} +cmake -G "Unix Makefiles" \ + -D CMAKE_BUILD_TYPE=Debug \ + -D CMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \ + -D CMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ \ + -D BOOST_ROOT=/cygdrive/d/workspace/third-party/boost_1_61_0/mingw \ + -D BOOST_LIBRARYDIR=/cygdrive/d/workspace/third-party/boost_1_61_0/mingw/stage/x64/debug/lib \ + -D Boost_USE_STATIC_LIBS=ON \ + ... +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='pwd' out=out1 %} +{% include jekyll-theme/shell.html cmd=cmd2 %} diff --git a/_posts/2017-06-24-static-vs-inline-vs-unnamed-namespaces.md b/_posts/2017-06-24-static-vs-inline-vs-unnamed-namespaces.md new file mode 100644 index 0000000..cb12867 --- /dev/null +++ b/_posts/2017-06-24-static-vs-inline-vs-unnamed-namespaces.md @@ -0,0 +1,273 @@ +--- +title: static vs. inline vs. namespace { +category: C++ +snippets_root_directory: snippets/static_vs_inline_vs_unnamed_namespaces +snippets_language: c++ +snippets: + static: + - static/main.cpp + - static/proxy.cpp + - static/proxy.hpp + - static/shared.hpp + inline: + - inline/shared.hpp + inline_weird: + - inline/weird/main.cpp + - inline/weird/another.cpp + - inline/weird/another.hpp + unnamed_namespaces_weird: + - unnamed_namespaces/weird/main.cpp + - unnamed_namespaces/weird/another.cpp + - unnamed_namespaces/weird/another.hpp + unnamed_namespaces_ok: + - unnamed_namespaces/ok/main.cpp + - unnamed_namespaces/ok/another.cpp + - unnamed_namespaces/ok/another.hpp + static_and_inline: + - static_and_inline/main.cpp + - static_and_inline/proxy.cpp + - static_and_inline/proxy.hpp + - static_and_inline/shared.hpp + unnamed_namespace_and_inline: + - unnamed_namespace_and_inline/main.cpp + - unnamed_namespace_and_inline/proxy.cpp + - unnamed_namespace_and_inline/proxy.hpp + - unnamed_namespace_and_inline/shared.hpp + separate_method_definitions: + - separate_method_definitions/main.cpp + - separate_method_definitions/another.cpp + - separate_method_definitions/another.hpp + - separate_method_definitions/shared.hpp +--- +In this post I'll try to figure out whether I should use `static`, `inline` or +unnamed namespaces for function definitions. + +TL;DR +----- + +Here's my attempt to build an algorithm to decide whether a class/function +should be defined with either of the `static`/`inline` specifiers or put into +an unnamed namespace. +The first question I answer is: is the entity defined in a header file or in a +.cpp file? + +* **In a header** — Is it a class or a function? + * **Class** — There's no need to do anything. + * **Function** — Do you want it to behave differently for each +translation unit (may be useful, for example, for logging)? + * **Yes** — Use `static`. + * **No** — Use `inline`. +* **In a .cpp file** — Put it into an unnamed namespace. + +`static` +-------- + +It's an old C-style method of defining functions in header files. +This way, every translation unit gets its own copy of a function. +What does that mean? +The most obvious implication that pops into my head is that every local static +variable defined inside that function gets an independent replica in every +translation unit. +For example, the program below would print + +``` +1 +1 +``` + +due to the fact that both main.cpp and proxy.cpp get their own versions of `n` +from `shared()`. + +{% include jekyll-theme/snippets/section.html section_id='static' %} + +In C, this is the only way to share function definitions between translation +units (apart from the usual way of declaring a function in a header file and +putting its definition to a .c file). + +### Properties + +* Using `static`, you can share function definitions between multiple +translation units. +* Each unit gets its own replica of the function: they have different +addresses, their local static variables are independent, etc. +* If different translation units define different functions with the same +name using the `static` specifier, each unit can use its function without any +issues. +This might seem like an trivial claim, but other approaches sometimes disallow +this, which is discussed below. + +`inline` +-------- + +It's well-known that this keyword has pretty much nothing to do with whether a +function will actually be inlined or not. +It's used much more often to define functions in header files, since every +function defined this way will be the same (as in "will have the same address") +in every translation unit. +Let's try and adjust the definition of `shared()` accordingly: + +{% include jekyll-theme/snippets/section.html section_id='inline' %} + +The same program would then print + +``` +1 +2 +``` + +since both `main()` and `proxy()` would call the same `shared()`, incrementing +the same `n`. + +Weird things happen when different translation units define different `inline` +functions with the same name. + +{% include jekyll-theme/snippets/section.html section_id='inline_weird' %} + +According to my simple experiments, this program produces different output +based on which .cpp file was specified first on the command line during +compilation. +For example, this is the output of test.exe produced with either `cl /W4 /EHsc +main.cpp another.cpp /Fe:test.exe` or `g++ -Wall -Wextra -std=c++11 main.cpp +another.cpp -o test.exe`. + +``` +main.cpp: shared() +main.cpp: shared() +``` + +If we swap the order of .cpp files (`another.cpp main.cpp` instead of `main.cpp +another.cpp`), the output becomes + +``` +another.cpp: shared() +another.cpp: shared() +``` + +No warnings/errors are emitted, making the situation truly disturbing. +I tested this with GNU compiler version 5.4.0 and Microsoft compiler version +19.00.24210. + +This behavior can be easily fixed either by making these functions `static` or +by using unnamed namespaces (see below). + +### Properties + +* Using `inline`, you can share function definitions between multiple +translation units. +* Each translation unit will use the same function: it will have the same +address in every translation unit, its local static variables will be shared, +etc. +* Defining different `inline` functions with the same name in different +translation units is undefined behavior. + +Two inline functions might be different even if they are the same textually. +For example, they might reference two global variables which have the same +name, but are defined in different translation units. +{: .alert .alert-info } + +`namespace {` +------------- + +With respect to function definitions, unnamed namespaces are, according to my +understanding, quite similar to the `static` keyword. +The additional value they provide is that they provide a way to apply `static` +not only to functions, but to classes also. +Remember the weirdness that happens when multiple translation units define +different `inline` functions with the same name? +Arguably, it gets even worse if we add classes to the equation. + +{% include jekyll-theme/snippets/section.html section_id='unnamed_namespaces_weird' %} + +Compiling this program the same way we did in the `inline` example (`cl /W4 +/EHsc main.cpp another.cpp /Fe:test.exe`/`g++ -Wall -Wextra -std=c++11 main.cpp +another.cpp -o test.exe`) yields different outputs depending on which .cpp file +was specified first. + +``` +main.cpp: Test::Test() +1 +main.cpp: Test::Test() +``` + +``` +another.cpp: Test::Test() +1065353216 +another.cpp: Test::Test() +``` + +I'm not sure why anybody would want that. +This can be easily fixed by putting both `Test` classes into unnamed +namespaces. +The program than reads + +{% include jekyll-theme/snippets/section.html section_id='unnamed_namespaces_ok' %} + +After the adjustment, it produces the same output regardless of compilation +options. + +``` +main.cpp: Test::Test() +1 +another.cpp: Test::Test() +``` + +Notice how sharing classes defined in header files isn't discussed here. +The standard actually guarantees that if a class is defined in a header file, +all translation units that use it share the definition. + +### Properties + +* Essentially, unnamed namespaces allow the `static` keyword to be applied to +classes. +* Similar to the `static` approach, each translation unit gets its own replica +of a function/class, including their own local static variables, etc. +* Defining different classes with the same name in different translation units +(without utilizing unnamed namespaces) is undefined behavior. + +Tricky cases +------------ + +### `static` + `inline` + +In case a function is defined as `static inline`, `static` wins, and `inline` +is ignored. +The program below outputs + +``` +1 +1 +``` + +{% include jekyll-theme/snippets/section.html section_id='static_and_inline' %} + +In general, I can't think of a reason to define a `static inline` function. + +### `namespace {` + `inline` + +If an `inline` function is defined in an unnamed namespace, the unnamed +namespace wins. +The program below outputs + +``` +1 +1 +``` + +{% include jekyll-theme/snippets/section.html section_id='unnamed_namespace_and_inline' %} + +In general, I can't think of a reason to define an `inline` function in an +unnamed namespace. + +### Separate method definitions + +If you want to separate your class declaration from its method definitions +while keeping them in the same header file, each method must be explicitly +defined `inline`. +The program below outputs + +``` +1 +2 +``` + +{% include jekyll-theme/snippets/section.html section_id='separate_method_definitions' %} diff --git a/_posts/2018-02-18-peculiar-indentation.md b/_posts/2018-02-18-peculiar-indentation.md new file mode 100644 index 0000000..c477382 --- /dev/null +++ b/_posts/2018-02-18-peculiar-indentation.md @@ -0,0 +1,101 @@ +--- +title: Peculiar Haskell indentation +category: Haskell +--- +I've fallen into a Haskell indentation pitfall. +I think it must be common, so I'm describing it here. + +The problem is that indentation rules in `do` blocks are not intuitive to me. +For example, the following function is valid Haskell syntax: + +```haskell +foo1 :: IO () +foo1 = + alloca $ \a -> + alloca $ \b -> + alloca $ \c -> do + poke a (1 :: Int) + poke b (1 :: Int) + poke c (1 :: Int) + return () +``` + +In fact, this funnier version is also OK: + +```haskell +foo2 :: IO () +foo2 = alloca $ \a -> + alloca $ \b -> + alloca $ \c -> do + poke a (1 :: Int) + poke b (1 :: Int) + poke c (1 :: Int) + return () +``` + +If you add an outer `do` however, things become a little more complicated. +For example, this is the valid version of the functions above with an outer +`do`: + +```haskell +foo3 :: IO () +foo3 = do + alloca $ \a -> + alloca $ \b -> + alloca $ \c -> do + poke a (1 :: Int) + poke b (1 :: Int) + poke c (1 :: Int) + return () +``` + +Notice the extra indentation for each of the `alloca`s. +When I tried to remove these seemingly excessive indents, GHC complained with +the usual `parse error (possibly incorrect indentation or mismatched +brackets)`. + +```haskell +foo4 :: IO () +foo4 = do + alloca $ \a -> + alloca $ \b -> + alloca $ \c -> do + poke a (1 :: Int) + poke b (1 :: Int) + poke c (1 :: Int) + return () +``` + +The truth is, the rules for desugaring `do` blocks are surprisingly simple and +literal. +GHC inserts semicolons according to the rules [found in the Wikibook]. +So it inserts semicolons between the `alloca`s on the same level, so `foo4` +becomes: + +```haskell +foo4 :: IO () +foo4 = do + { alloca $ \a -> + ; alloca $ \b -> + ; alloca $ \c -> do + { poke a (1 :: Int) + ; poke b (1 :: Int) + ; poke c (1 :: Int) + ; return () + } + } +``` + +[found in the Wikibook]: https://en.wikibooks.org/wiki/Haskell/Indentation#Explicit_characters_in_place_of_indentation + +The semicolons after `->` are clearly invalid Haskell syntax, hence the error. + +P.S. To compile the functions above, you need to include them in a module and +add proper imports, e.g. + +```haskell +module PeculiarIndentation where + +import Foreign.Marshal.Alloc (alloca) +import Foreign.Storable (poke) +``` diff --git a/_posts/2020-02-24-ssh-tunnel-windows.md b/_posts/2020-02-24-ssh-tunnel-windows.md new file mode 100644 index 0000000..fdbc134 --- /dev/null +++ b/_posts/2020-02-24-ssh-tunnel-windows.md @@ -0,0 +1,153 @@ +--- +title: Persistent SSH tunnel +--- +SSH tunneling is awesome. +For some reason, I've only recently learned about this feature, but I've been +immediately blown away by how useful it can be. + +Basically, to use SSH tunneling (a.k.a. port forwarding) you need to have a SSH +client (`ssh`) with an access to a SSH server. +You can then access any host your SSH server has access to. +It works like this: + +* the client establishes a connection to the SSH server, +* the client asks the server to forward incoming requests to the destination +host, +* the client listens on a proxy port on the local machine, and forwards +requests to the SSH server. + +Say, you have access to SSH server `gateway` on port 22, and you want to gain +access to HTTPS server `dest` on port 443, which is only accessible from the +the SSH server. +You can then run something like + +{% include jekyll-theme/shell.html cmd='ssh -L 4433:dest:443 gateway -p 22' %} + +And now you can access `dest` at `https://localhost:4433/`. +That's brilliant, really. + +But there's more. +You can make a _reverse_ tunnel, allowing you to give access to any host your +client computer has access to, via a remote SSH server. +It works like this: + +* your SSH client establishes a connection to the SSH server, +* the client asks the server to listen on a port of your choosing and forward +incoming requests to the client, +* the client forwards incoming requests to the destination host. + +This, as I've recently learned, is a common pattern to subvert corporate +firewalls, which frequently forbid incoming connections. +Say, you want to access your work computer from home via RDP. +Both your home and your work computers have access to a SSH server `gateway` on +port 22 (you might want to change it to port 80 or 443 if your outside +connections are filtered). + +You can then run something like (notice the `-R`) + +{% include jekyll-theme/shell.html cmd='ssh -R 13389:127.0.0.1:3389 gateway -p 22' %} + +and now you can connect to `gateway:13389` from your home computer using a RDP +client. +Even more brilliant! + +You might need to set the `GatewayPorts` setting to `yes` or `clientspecified` +on your SSH server (typically in "/etc/ssh/sshd_config"). + +Batch mode +---------- + +If you want to establish a reverse SSH tunnel automatically, some tweaking is +required. +First, set some SSH client options: + +* `-F /dev/null` to disregard the user config, +* `-oBatchMode=yes` to run non-interactively, +* `-oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null` to disable server +verification (optional), +* `-oExitOnForwardFailure=yes` to exit if port forwarding fails, +* `-oServerAliveCountMax=3 -oServerAliveInterval=15` to break the connection if +the server or the network is down, +* `-N -n -T` to only forward the ports and not execute the shell or any +additional commands. + +Thus, the full command would be something like + +{% capture cmd1 %} +ssh \ + -F /dev/null \ + -oBatchMode=yes \ + -oStrictHostKeyChecking=no \ + -oUserKnownHostsFile=/dev/null \ + -oExitOnForwardFailure=yes \ + -oServerAliveCountMax=3 \ + -oServerAliveInterval=15 \ + -N -n -T \ + -R 13389:127.0.0.1:3389 \ + user@gateway -p 22 \ + -i ~/.ssh/tunnel +{% endcapture %} + +{% include jekyll-theme/shell.html cmd=cmd1 %} + +Adjust the `user@gateway -p 22` part accordingly. + +Notice also `-i ~/.ssh/tunnel`. +It's the path to the SSH key used to authenticate with the server. +It can't have a passphrase, since the command will be run non-interactively, +and the public key must be in the server's authorized_keys file. + +For best results, you should also adjust some settings on the SSH server. +Namely, you should enable client keep-alives on the server using something like + +``` +ClientAliveCountMax 3 +ClientAliveInterval 15 +``` + +Unless you do that, even if the client breaks the connection, you won't be able +to re-establish it for a long-ish time, since the server wouldn't know that the +original connection is no longer valid. + +As a service +------------ + +Cygwin is awesome. +I've been using for 10+ years, and it has never failed me. +It comes with a SSH server, a client (you need to install the `openssh` package +for both of these), and a service manager, `cygrunsrv`. +`cygrunsrv` is similar to [NSSM], as it allows to wrap any executable into a +native Windows service. + +[NSSM]: https://nssm.cc/ + +Using `cygrunsrv`, you can create a Windows service to establish a reverse SSH +tunnel automatically. + +{% capture cmd1 %} +cygrunsrv \ + -I ssh_tunnel \ + -p /usr/bin/ssh \ + --args '-F /dev/null -oBatchMode=yes -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oExitOnForwardFailure=yes -oServerAliveCountMax=3 -oServerAliveInterval=15 -N -n -T -R 13389:127.0.0.1:3389 user@gateway -p 22 -i ~/.ssh/tunnel' \ + --disp 'Reverse SSH tunnels' \ + --user user \ + --neverexits \ + --preshutdown +{% endcapture %} + +{% include jekyll-theme/shell.html cmd=cmd1 %} + +Adjust the `--user` and the `--args` values accordingly. + +You can then run `services.msc` and adjust the recovery settings for the +service to restart if `ssh` fails: + +<div class="row"> + <div class="col-xs-12 col-sm-8 col-md-6"> + <a href="{{ '/assets/img/ssh_tunnel_services.png' | relative_url }}" class="thumbnail"> + <img class="img-responsive" alt="services.msc" src="{{ '/assets/img/ssh_tunnel_services.png' | relative_url }}"> + </a> + </div> +</div> + +And voilà , you have an automatic reverse SSH tunnel on Windows for you! diff --git a/_posts/2020-05-06-docker-bind-mounts.md b/_posts/2020-05-06-docker-bind-mounts.md new file mode 100644 index 0000000..832c132 --- /dev/null +++ b/_posts/2020-05-06-docker-bind-mounts.md @@ -0,0 +1,227 @@ +--- +title: 'Docker: bind mounts & file ownership' +--- +If you want to: + +1. run your Docker service as a user other than root, +2. share a writable directory between your host and the container, + +you're in for a treat! +The thing is, files stored in the shared directory retain their ownership (and +by that I mean their UIDs and GIDs, as they're the only thing that matters) +after being mounted in the container. + +Case in point: + +{% capture cmd1 %} +docker run -it --rm -v "$( pwd ):/data" alpine touch /data/test.txt +{% endcapture %} + +{% include jekyll-theme/shell.html cmd=cmd1 %} + +would create file ./test.txt owned by root:root. + +You can fix that by using the `--user` parameter: + +{% capture cmd1 %} +docker run -it --rm -v "$( pwd ):/data" --user "$( id -u ):$( id -g )" alpine touch /data/test.txt +{% endcapture %} + +{% include jekyll-theme/shell.html cmd=cmd1 %} + +That would create file ./test.txt owned by the current user (if the current +working directory is writable by the current user, of course). + +More often though, instead of a simple `touch` call, you have a 24/7 service, +which absolutely mustn't run as root, regardless of whether `--user` was +specified or not. +In such cases, the logical solution would be to create a regular user in the +container, and use it to run the service. +In fact, that's what many popular images do, i.e. [Redis][Redis Dockerfile] and +[MongoDB][MongoDB Dockerfile]. + +[Redis Dockerfile]: https://github.com/docker-library/redis/blob/cc1b618d51eb5f6bf6e3a03c7842317b38dbd7f9/6.0/Dockerfile#L4 +[MongoDB Dockerfile]: https://github.com/docker-library/mongo/blob/5cbf7be9a486932b7e472a39e432c9a444628465/4.2/Dockerfile#L4 + +How do you run the service as regular user though? +It's tempting to use the `USER` directive in the Dockerfile, but that can be +overridden by `--user`: + +{% capture cmd1 %} +cat Dockerfile +{% endcapture %} +{% capture out1 %} +FROM alpine + +RUN addgroup --gid 9099 test-group && \ + adduser \ + --disabled-password \ + --gecos '' \ + --home /home/test-user \ + --ingroup test-group \ + --uid 9099 \ + test-user + +RUN touch /root.txt +USER test-user:test-group +RUN touch /home/test-user/test-user.txt + +CMD id && stat -c '%U %G' /root.txt && stat -c '%U %G' /home/test-user/test-user.txt +{% endcapture %} + +{% capture cmd2 %} +docker build -t id . +{% endcapture %} + +{% capture cmd3 %} +docker run -it --rm id +{% endcapture %} +{% capture out3 %} +uid=9099(test-user) gid=9099(test-group) +root root +test-user test-group +{% endcapture %} + +{% capture cmd4 %} +docker run -it --rm --user root id +{% endcapture %} +{% capture out4 %} +uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video) +root root +test-user test-group +{% endcapture %} + +{% include jekyll-theme/shell.html cmd=cmd1 out=out1 %} +{% include jekyll-theme/shell.html cmd=cmd2 %} +{% include jekyll-theme/shell.html cmd=cmd3 out=out3 %} +{% include jekyll-theme/shell.html cmd=cmd4 out=out4 %} + +I suppose that's the reason why many popular images override ENTRYPOINT, using +a custom script (and `gosu`, which is basically `sudo`, I think) to forcefully +drop privileges (for example, see [Redis][Redis entrypoint], +[MongoDB][MongoDB entrypoint]). + +[Redis entrypoint]: https://github.com/docker-library/redis/blob/cc1b618d51eb5f6bf6e3a03c7842317b38dbd7f9/6.0/docker-entrypoint.sh#L11 +[MongoDB entrypoint]: https://github.com/docker-library/mongo/blob/5cbf7be9a486932b7e472a39e432c9a444628465/4.2/docker-entrypoint.sh#L12 + +Now, what if such service needs persistent storage? +A good solution would be to use Docker volumes. +For development though, you often need to just share a directory between your +host and the container, and it has to be writable by both the host and the +container process. +This can be accomplished using _bind mounts_. +For example, let's try to map ./data to /data inside a Redis container (this +assumes ./data doesn't exist and you're running as regular user with UID 1000; +press Ctrl+C to stop Redis): + +{% capture cmd1 %} +mkdir data +{% endcapture %} + +{% capture cmd2 %} +stat -c '%u' data +{% endcapture %} +{% capture out2 %} +1000 +{% endcapture %} + +{% capture cmd3 %} +docker run -it --rm --name redis -v "$( pwd )/data:/data" redis:6.0 +{% endcapture %} + +{% capture cmd4 %} +stat -c '%u' data +{% endcapture %} +{% capture out4 %} +999 +{% endcapture %} + +{% include jekyll-theme/shell.html cmd=cmd1 %} +{% include jekyll-theme/shell.html cmd=cmd2 out=out2 %} +{% include jekyll-theme/shell.html cmd=cmd3 %} +{% include jekyll-theme/shell.html cmd=cmd4 out=out4 %} + +As you can see, ./data changed its owner from user with UID 1000 (the host +user) to user with UID 999 (the `redis` user inside the container). +This is done in Redis' ENTRYPOINT script, just before dropping root privileges +so that the `redis-server` process owns the /data directory and thus can write +to it. + +If you want to preserve ./data ownership, Redis' image (and many others) +explicitly accommodates for it by _not_ changing its owner if the container is +run as anybody other than root. +For example: + +{% capture cmd1 %} +mkdir data +{% endcapture %} + +{% capture cmd2 %} +stat -c '%u' data +{% endcapture %} +{% capture out2 %} +1000 +{% endcapture %} + +{% capture cmd3 %} +docker run -it --rm --name redis -v "$( pwd )/data:/data" --user "$( id -u ):$( id -g )" redis:6.0 +{% endcapture %} + +{% capture cmd4 %} +stat -c '%u' data +{% endcapture %} +{% capture out4 %} +1000 +{% endcapture %} + +{% include jekyll-theme/shell.html cmd=cmd1 %} +{% include jekyll-theme/shell.html cmd=cmd2 out=out2 %} +{% include jekyll-theme/shell.html cmd=cmd3 %} +{% include jekyll-theme/shell.html cmd=cmd4 out=out4 %} + +Going hardcore +-------------- + +Sometimes `--user` is not enough though. +The specified user is almost certainly missing from container's /etc/passwd, it +doesn't have a $HOME directory, etc. +All of that could cause problems with some applications. + +The solution often suggested is to create a container user with a fixed UID +(that would match the host user UID). +That way, the app won't be run as root, the user will have a proper entry in +/etc/passwd, it will be able to write to the bind mount owned by the host user, +and it won't have to change the directory's permissions. + +We can create a user with a fixed UID when + +1. building the image (using build `ARG`uments), +2. first starting the container by passing the required UID using environment +variables. + +The advantage of creating the user when building the image is that we can also +do additional work in the Dockerfile (like if you need to install dependencies +as that user). +The disadvantage is that the image would need to be rebuilt for every user on +every machine. + +Creating the user when first starting the container switches the pros and cons. +You don't need to rebuild the image every time, but you'll have to waste time +and resources by doing the additional work that could've been done in the +Dockerfile every time you create a container. + +For my project [jekyll-docker] I opted for the former approach, making sure the +`jekyll` process runs with the same UID as the user who built the image (unless +it was built by root, in which case it falls back to a custom UID of 999). +Seems to work quite nicely in practice. + +[jekyll-docker]: https://github.com/egor-tensin/jekyll-docker/tree/7d1824a5fac0ed483bc49209bbd89f564a7bcefe + +Useful links +------------ + +* [Docker and \-\-userns-remap, how to manage volume permissions to share data between host and container?](https://stackoverflow.com/q/35291520/514684) +* [What is the (best) way to manage permissions for Docker shared volumes?](https://stackoverflow.com/q/23544282/514684) +* [Handling Permissions with Docker Volumes](https://denibertovic.com/posts/handling-permissions-with-docker-volumes/) +* [File Permissions: the painful side of Docker](https://blog.gougousis.net/file-permissions-the-painful-side-of-docker/) +* [Avoiding Permission Issues With Docker-Created Files](https://vsupalov.com/docker-shared-permissions/) diff --git a/_posts/2020-05-20-makefile-escaping.md b/_posts/2020-05-20-makefile-escaping.md new file mode 100644 index 0000000..d468cc3 --- /dev/null +++ b/_posts/2020-05-20-makefile-escaping.md @@ -0,0 +1,439 @@ +--- +title: Escaping characters in Makefile +--- +TL;DR: visit [this page] for a short and concise version of this article. +{: .alert .alert-success } + +[this page]: {% link _notes/makefile.md %} + +I'm a big sucker for irrelevant nitpicks like properly quoting arguments in +shell scripts. +I've also recently started using GNU make as a substitute for one-line shell +scripts (so instead of a bunch of scripts like build.sh, deploy.sh, test.sh I +get to have a single Makefile and can just run `make build`, `make deploy`, +`make test`). + +As a side note, there's an excellent [Makefile style guide] available on the +web. +I'm going to be using a slightly modified prologue suggested in the guide in +all Makefiles in this post: + +[Makefile style guide]: https://clarkgrubb.com/makefile-style-guide + +``` +MAKEFLAGS += --no-builtin-rules --no-builtin-variables --warn-undefined-variables +unexport MAKEFLAGS +.DEFAULT_GOAL := all +.DELETE_ON_ERROR: +.SUFFIXES: +SHELL := bash +.SHELLFLAGS := -eu -o pipefail -c +``` + +`make` invokes a shell program to execute recipes. +As issues of properly escaping "special" characters are going to be discussed, +the choice of shell is very relevant. +The Makefiles in this post specify `bash` explicitly using the `SHELL` +variable, but the same rules should apply for all similar `sh`-like shells. + +Quoting arguments +----------------- + +You should quote command arguments in `make` rule recipes, just like in shell +scripts. +This is to prevent a single argument from being expanded into multiple +arguments by the shell. + +{% capture out1 %} +# Prologue goes here... + +test_var := Same line? +export test_var + +test: + @printf '%s\n' $(test_var) + @printf '%s\n' '$(test_var)' + @printf '%s\n' $$test_var + @printf '%s\n' "$$test_var" +{% endcapture %} + +{% capture out2 %} +Same +line? +Same line? +Same +line? +Same line? +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cat Makefile' out=out1 %} +{% include jekyll-theme/shell.html cmd='make test' out=out2 %} + +This is quite often sufficient to write valid recipes. + +One thing to note is that you shouldn't use double quotes `"` for quoting +arguments, as they might contain literal dollar signs `$`, interpreted by the +shell as variable references, which is not something you always want. + +Escaping quotes +--------------- + +What if `test_var` included a single quote `'`? +In that case, even the quoted `printf` invocation would break because of the +mismatch. + +{% capture out1 %} +# Prologue goes here... + +test_var := Includes ' quote + +test: + printf '%s\n' '$(test_var)' +{% endcapture %} + +{% capture out2 %} +printf '%s\n' 'Includes ' quote' +bash: -c: line 0: unexpected EOF while looking for matching `'' +make: *** [Makefile:11: test] Error 2 +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cat Makefile' out=out1 %} +{% include jekyll-theme/shell.html cmd='make test' out=out2 %} + +One solution is to take advantage of how `bash` parses command arguments, and +replace every quote `'` by `'\''`. +This works because `bash` merges a string like `'Includes '\'' quote'` into +`Includes ' quote`. + +{% capture out1 %} +# Prologue goes here... + +escape = $(subst ','\'',$(1)) + +test_var := Includes ' quote + +test: + printf '%s\n' '$(call escape,$(test_var))' +{% endcapture %} + +{% capture out2 %} +printf '%s\n' 'Includes '\'' quote' +Includes ' quote +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cat Makefile' out=out1 %} +{% include jekyll-theme/shell.html cmd='make test' out=out2 %} + +Surprisingly, this works even in much more complicated cases. +You can have a recipe that executes a command that takes a whole other command +(with its own separate arguments) as an argument. +I guess the most common use case is doing something like `ssh 'rm -rf +$(junk_dir)'`, but I'll use nested `bash` calls instead for simplicity. + +{% capture out1 %} +# Prologue goes here... + +escape = $(subst ','\'',$(1)) + +test_var := Includes ' quote + +echo_test_var := printf '%s\n' '$(call escape,$(test_var))' +bash_test_var := bash -c '$(call escape,$(echo_test_var))' + +test: + printf '%s\n' '$(call escape,$(test_var))' + bash -c '$(call escape,$(echo_test_var))' + bash -c '$(call escape,$(bash_test_var))' +{% endcapture %} + +{% capture out2 %} +printf '%s\n' 'Includes '\'' quote' +Includes ' quote +bash -c 'printf '\''%s\n'\'' '\''Includes '\''\'\'''\'' quote'\''' +Includes ' quote +bash -c 'bash -c '\''printf '\''\'\'''\''%s\n'\''\'\'''\'' '\''\'\'''\''Includes '\''\'\'''\''\'\''\'\'''\'''\''\'\'''\'' quote'\''\'\'''\'''\''' +Includes ' quote +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cat Makefile' out=out1 %} +{% include jekyll-theme/shell.html cmd='make test' out=out2 %} + +That's somewhat insane, but it works. + +Shell output +------------ + +The `shell` function is one of the two most common ways to communicate with the +outside world in a Makefile (the other being environment variables). +This little `escape` function we've defined is actually sufficient to deal with +the output of the `shell` function safely. + +{% capture out1 %} +# Prologue goes here... + +escape = $(subst ','\'',$(1)) + +cwd := $(shell basename -- "$$( pwd )") + +simple_var := Simple value +composite_var := Composite value - $(simple_var) - $(cwd) + +.PHONY: test +test: + @printf '%s\n' '$(call escape,$(cwd))' + @printf '%s\n' '$(call escape,$(composite_var))' +{% endcapture %} + +{% capture cmd2 %} +mkdir "Includes ' quote" && \ + cd "Includes ' quote" && \ + make -f ../Makefile test +{% endcapture %} +{% capture out2 %} +Includes ' quote +Composite value - Simple value - Includes ' quote +{% endcapture %} + +{% capture cmd3 %} +mkdir 'Maybe a comment #' && \ + cd 'Maybe a comment #' && \ + make -f ../Makefile test +{% endcapture %} +{% capture out3 %} +Maybe a comment # +Composite value - Simple value - Maybe a comment # +{% endcapture %} + +{% capture cmd4 %} +mkdir 'Variable ${reference}' && \ + cd 'Variable ${reference}' && \ + make -f ../Makefile test +{% endcapture %} +{% capture out4 %} +Variable ${reference} +Composite value - Simple value - Variable ${reference} +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cat Makefile' out=out1 %} +{% include jekyll-theme/shell.html cmd=cmd2 out=out2 %} +{% include jekyll-theme/shell.html cmd=cmd3 out=out3 %} +{% include jekyll-theme/shell.html cmd=cmd4 out=out4 %} + +Environment variables +--------------------- + +Makefiles often have parameters that modify their behaviour. +The most common example is doing something like `make install +PREFIX=/somewhere/else`, where the `PREFIX` argument overrides the default +value "/usr/local". +These parameters are often defined in a Makefile like this: + +``` +param_name ?= Default value +``` + +They should be `escape`d and quoted when passed to external commands, of +course. +However, things get complicated when they contain dollar signs `$`. +`make` variables may contain references to other variables, and they're +expanded recursively either when defined (for `:=` assignments) or when used +(in all other cases, including `?=`). + +{% capture out1 %} +# Prologue goes here... + +escape = $(subst ','\'',$(1)) + +test_var ?= This is safe. +export test_var + +.PHONY: test +test: + @printf '%s\n' '$(call escape,$(test_var))' + @printf '%s\n' "$$test_var" +{% endcapture %} + +{% capture cmd2 %} +test_var='Variable ${reference}' make test +{% endcapture %} +{% capture out2 %} +Makefile:15: warning: undefined variable 'reference' +Variable +Variable ${reference} +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cat Makefile' out=out1 %} +{% include jekyll-theme/shell.html cmd=cmd2 out=out2 %} + +Here, `$(test_var)` is expanded recursively, substituting an empty string for +the `${reference}` part. +One attempt to solve this is to escape the dollar sign in the variable value, +but that breaks the `"$$test_var"` case: + +{% capture cmd1 %} +test_var='Variable $${reference}' make test +{% endcapture %} +{% capture out1 %} +Variable ${reference} +Variable $${reference} +{% endcapture %} + +{% include jekyll-theme/shell.html cmd=cmd1 out=out1 %} + +A working solution would be to use the `escape` function on the unexpanded +variable value. +Turns out, you can do just that using the `value` function in `make`. + +{% capture out1 %} +# Prologue goes here... + +escape = $(subst ','\'',$(1)) + +test_var ?= This is safe. +test_var := $(value test_var) +export test_var + +.PHONY: test +test: + @printf '%s\n' '$(call escape,$(test_var))' + @printf '%s\n' "$$test_var" +{% endcapture %} + +{% capture cmd2 %} +test_var="Quote '"' and variable ${reference}' make test +{% endcapture %} +{% capture out2 %} +Quote ' and variable ${reference} +Quote ' and variable ${reference} +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cat Makefile' out=out1 %} +{% include jekyll-theme/shell.html cmd=cmd2 out=out2 %} + +This doesn't quite work though when [overriding variables] on the command line. +For example, this doesn't work: + +[overriding variables]: https://www.gnu.org/software/make/manual/html_node/Overriding.html#Overriding + +{% capture cmd1 %} +make test test_var='Variable ${reference}' +{% endcapture %} +{% capture out1 %} +Makefile:16: warning: undefined variable 'reference' +make: warning: undefined variable 'reference' +Variable +Variable +{% endcapture %} + +{% include jekyll-theme/shell.html cmd=cmd1 out=out1 %} + +This is because `make` ignores all assignments to `test_var` if it's overridden +on the command line (including `test_var := $(value test_var)`). + +This can be fixed using the `override` directive for these cases only. +A complete solution that works for seemingly all cases looks like something +along these lines: + +``` +ifeq ($(origin test_var),environment) + test_var := $(value test_var) +endif +ifeq ($(origin test_var),environment override) + test_var := $(value test_var) +endif +ifeq ($(origin test_var),command line) + override test_var := $(value test_var) +endif +``` + +Here, we check where the value of `test_var` comes from using the `origin` +function. +If it was defined in the environment (the `environment` and `environment +override` cases), its value is prevented from being expanded using the `value` +function. +If it was overridden on the command line (the `command line` case), the +`override` directive is used so that the unexpanded value actually gets +assigned. + +The snippet above can be generalized by defining a custom function that +produces the required `make` code, and then calling `eval`. + +``` +define noexpand +ifeq ($$(origin $(1)),environment) + $(1) := $$(value $(1)) +endif +ifeq ($$(origin $(1)),environment override) + $(1) := $$(value $(1)) +endif +ifeq ($$(origin $(1)),command line) + override $(1) := $$(value $(1)) +endif +endef + +test_var ?= This is safe. + +$(eval $(call noexpand,test_var)) +``` + +I couldn't find a case where the combination of `escape` and `noexpand` +wouldn't work. +You can even safely use other variable as the default value of `test_var`, and +it works: + +{% capture out1 %} +# Prologue goes here... + +escape = $(subst ','\'',$(1)) + +define noexpand +ifeq ($$(origin $(1)),environment) + $(1) := $$(value $(1)) +endif +ifeq ($$(origin $(1)),environment override) + $(1) := $$(value $(1)) +endif +ifeq ($$(origin $(1)),command line) + override $(1) := $$(value $(1)) +endif +endef + +simple_var := Simple value + +test_var ?= $(simple_var) in test_var +$(eval $(call noexpand,test_var)) + +simple_var := New simple value +composite_var := Composite value - $(simple_var) - $(test_var) + +.PHONY: test +test: + @printf '%s\n' '$(call escape,$(test_var))' + @printf '%s\n' '$(call escape,$(composite_var))' +{% endcapture %} + +{% capture out2 %} +New simple value in test_var +Composite value - New simple value - New simple value in test_var +{% endcapture %} + +{% capture cmd3 %} +make test test_var='Variable ${reference}' +{% endcapture %} +{% capture out3 %} +Variable ${reference} +Composite value - New simple value - Variable ${reference} +{% endcapture %} + +{% capture cmd4 %} +test_var='Variable ${reference}' make test +{% endcapture %} +{% capture out4 %} +Variable ${reference} +Composite value - New simple value - Variable ${reference} +{% endcapture %} + +{% include jekyll-theme/shell.html cmd='cat Makefile' out=out1 %} +{% include jekyll-theme/shell.html cmd='make test' out=out2 %} +{% include jekyll-theme/shell.html cmd=cmd3 out=out3 %} +{% include jekyll-theme/shell.html cmd=cmd4 out=out4 %} diff --git a/_posts/2021-03-10-ubuntu-packaging.md b/_posts/2021-03-10-ubuntu-packaging.md new file mode 100644 index 0000000..ad94f2a --- /dev/null +++ b/_posts/2021-03-10-ubuntu-packaging.md @@ -0,0 +1,320 @@ +--- +title: Basic Ubuntu packaging +snippets_root_directory: snippets/ubuntu_packaging +snippets_language: plain +snippets: + basic: + - basic/changelog + - basic/control + - basic/copyright + - basic/rules + install: + - install/test.install + gbp: + - gbp/gbp.conf +--- +It took me about an hour to make a PKGBUILD for my simple, non-compiled +piece of software to be published on [AUR]. +In contrast, it took me a few days to figure out how to build suitable .deb +packages for publishing in a PPA on [Launchpad]. +In this post, I'll try to describe some of the initial pain points of mine. + +[AUR]: https://aur.archlinux.org/ +[Launchpad]: https://launchpad.net/ + +Basics +------ + +The Debian package format is really old, and it shows. +There's a billion of metadata files to take care of, and barely any suitable +tutorials for beginners. +At best, you'll learn how to build _binary_ packages, not suitable for +publishing in a PPA (which only accept _source_ packages and builds the +binaries itself). + +First, you need to realize that there are source packages and binary packages. +Binary packages are the .deb files that actually contain the software. +A source package is, confusingly, multiple files, and you need to submit them +all to Launchpad. +You can distribute binary packages directly to your users, but they would have +to fetch & install the new version manually every time there's an upgrade. +If you could set up a repository and just point the users to it, they would get +the new versions naturally via the package manager (`apt`). + +Canonical's Launchpad provides a very handy PPA (Personal Package Archive) +service so that anyone can set up a repository. +Users could then use `add-apt-repository ppa:...` and get the packages in a +standard and convenient way. + +Tools +----- + +There's a myriad of tools to build and maintain Debian packages. +The [Debian New Maintainers' Guide] provides a [short summary] of how these +tools interact. +This tutorial assumes that your software lives in a Git repository and you'd +like to use Git to maintain the packaging metadata in the same repository. +This process is greatly aided by the [git-buildpackage] tool. +We still need to install a bunch of other stuff though; the complete command +line to install the required tools would be something like + +{% include jekyll-theme/shell.html cmd='sudo apt install -y build-essential devscripts dh-make git-buildpackage' %} + +Many of the tools pick up particular metadata (like the maintainer name and +email address) from environment variables. +You can put something like + + export DEBFULLNAME='John Doe' + export DEBEMAIL='John.Doe@example.com' + +in your .bashrc to set them globally. + +[Debian New Maintainers' Guide]: https://www.debian.org/doc/manuals/maint-guide +[short summary]: https://www.debian.org/doc/manuals/maint-guide/build.en.html#hierarchy +[git-buildpackage]: http://honk.sigxcpu.org/projects/git-buildpackage/manual-html/gbp.html + +Getting started +--------------- + +Let's create a repository to try things out. +It'll contain a single executable shell script test.sh, which only outputs the +string "test". + +{% include jekyll-theme/shell.html cmd='mkdir test' %} +{% include jekyll-theme/shell.html cmd='cd test' %} +{% include jekyll-theme/shell.html cmd='git init' %} + +{% capture cmd1 %} +cat <<'EOF' > test.sh +#!/usr/bin/env bash +echo test +EOF +{% endcapture %} +{% include jekyll-theme/shell.html cmd=cmd1 %} + +{% include jekyll-theme/shell.html cmd='chmod +x test.sh' %} +{% include jekyll-theme/shell.html cmd='git add .' %} +{% include jekyll-theme/shell.html cmd='git commit -m \'initial commit\'' %} + +This is going to be version 1.0 of our project, let's tag it as such. + +{% include jekyll-theme/shell.html cmd='git tag -a -m \'Release 1.0\' v1.0' %} + +All of the Debian packaging tools are tailored to the following use-case. + +1. There's an upstream distribution, which releases the software in tarballs. +2. There's a maintainer (who's not the software author), who takes care of +packaging and is disconnected from the development. + +This disconnect means that maintaining the Debian packaging files in the +`master` branch is inconvenient using the existing tools. +At the very least, you should create a separate branch for doing packaging +work. + +In addition, Debian (and hence, Ubuntu) is not a rolling-release distribution. +That means that there're regular releases, and the software version shouldn't +change too much during a lifetime of a single release. +Once Debian makes a release, the software version is more or less fixed, and +security fixes from future versions should be backported separately for each of +the supported Debian/Ubuntu releases. + +Except there _is_ a rolling-release distribution of Debian, and it's called +"unstable" or "sid". +The bleeding-edge packaging work should target the "unstable" distribution. + +So, let's create a new branch `debian` for our packaging work: + +{% include jekyll-theme/shell.html cmd='git checkout -b debian' %} + +All the packaging tools assume there's a separate folder "debian" that contains +the package metadata files. +There's a handy tool `dh_make` that creates the directory and populates it with +a number of template metadata files. +Using it is not so simple though. +First of all, it assumes that there's a properly named tarball with the project +sources available in the parent directory. +Why? +Who knows. +Let's create said tarball: + +{% include jekyll-theme/shell.html cmd='git archive --format=tar --prefix=test_1.0/ v1.0 | gzip -c > ../test_1.0.orig.tar.gz' %} + +The tarball name should follow the NAME_VERSION.orig.tar.gz pattern exactly! +Anyway, now is the time to run `dh_make`: + +{% include jekyll-theme/shell.html cmd='dh_make --indep --copyright mit --packagename test_1.0 --yes' %} + +I'm using the MIT License for our little script, hence the `--copyright mit` +argument. +In addition, every package in Debian is either "single", "arch-independent", +"library" or "python". +I'm not sure what the exact differences between those are, but a shell script +is clearly CPU architecture-independent, hence the `--indep` argument. +If it was a compiled executable, it would be a "single" (`--single`) package. + +`dh_make` created the "debian" directory for us, filled with all kinds of +files. +The only required ones are "changelog", "control", "source", "rules" and the +"source" directory. +Let's remove every other file for now: + +{% include jekyll-theme/shell.html cmd='rm -f -- debian/*.ex debian/*.EX debian/README.* debian/*.docs' %} + +You can study the exact format of the metadata files in the [Debian New +Maintainers' Guide], but for now let's keep it simple: + +{% include jekyll-theme/snippets/section.html section_id='basic' %} + +The "control" and "copyright" files are fairly straighforward. +The "changelog" file has a strict format and is supposed to be maintained using +the `dch` tool (luckily, git-buildpackage helps with that; more on that later). + +The "rules" file is an _executable_ Makefile, and actually controls how the +software is built. +Building a package involves invoking many predefined targets in this Makefile; +for now, we'll resort to delegating everything to the `dh` tool. +It's the Debhelper tool; it's a magic set of scripts that contain an +unbelievable amount of hidden logic that's supposed to aid package maintainers +in building the software. +For example, if the package is supposed to be built using the standard +`./configure && make && make install` sequence, it'll do this automatically. +If it's a Python package with setup.py, it'll use the Python package-building +utilities, etc. +We don't want any of that, we just want to copy test.sh to /usr/bin. +It can be taken care of using the `dh_install` script. +While building the package, it'll get executed by `dh`, read the +"debian/test.install" file and copy the files listed there to the specified +directories. +Our test.install should look like this: + +{% include jekyll-theme/snippets/section.html section_id='install' %} + +At this point, we can actually build a proper Debian package! + +{% include jekyll-theme/shell.html cmd='dpkg-buildpackage -uc -us' %} + +This command will generate a bunch of files in the parent directory. +The one of interest to us is "test_1.0-1_all.deb". +We can install it using `dpkg`: + +{% include jekyll-theme/shell.html cmd='sudo dpkg -i ../test_1.0-1_all.deb' %} + +We can now execute `test.sh`, and it'll hopefully print the string "test". + +This .deb file can be distributed to other users, but is no good for uploading +to Launchpad. +For one, it's a binary package, and we need source packages for Launchpad to +build itself. +Second, it's unsigned, which is also a no-no. + +I'm not going to describe how to set up a GnuPG key and upload it to the Ubuntu +keyserver (keyserver.ubuntu.com), but it's pretty straightforward once you know +the basics of GnuPG key handling. + +One disadvantage of the `dpkg-buildpackage` tool is that it creates a lot of +files in the "debian" directory; their purpose is unclear to me. +For now, you can delete them, leaving only the original "changelog", "control", +"copyright", "rules", "test.install" and the "source" directory. + +git-buildpackage +---------------- + +git-buildpackage is a wonderful tool that helps with managing the packaging +work in the upstream repository. +Please refer to its manual to learn how to use it properly. +We need to configure it so that it knows how the release tags look like +(`vVERSION`), how the packaging branch is called (`debian`) and where to put +the generated files. +Create "debian/gbp.conf" with the following contents: + +{% include jekyll-theme/snippets/section.html section_id='gbp' %} + +One unclear line here is `pristine-tar = False`. +It turns out, a lot of Debian package maintainers use the `pristine-tar` tool +to create "pristine", byte-for-byte reproducible tarballs of the upstream +software. +This is just more headache for us, so we're not going to use that; +git-buildpackage will just use the normal `git archive` to create tarballs. + +First, commit the packaging work we just made: + +{% include jekyll-theme/shell.html cmd='git add debian/' %} +{% include jekyll-theme/shell.html cmd='git commit -m \'initial Debian release\'' %} + +We can now build the package using git-buildpackage: + +{% include jekyll-theme/shell.html cmd='gbp buildpackage' %} + +The tool will try to sign the packages, so this assumes that you have your +GnuPG key set up! + +If all went right, it just built the packages in the ../build-area directory. +And it hasn't crapped all over the working directory too! +Similar to `dpkg-buildpackage`, it builds binary packages by default. +To build _source_ packages, it needs to be invoked with the `-S` argument: + +{% include jekyll-theme/shell.html cmd='gbp buildpackage -S' %} + +It'll build the source package in the same directory (you'll notice a lot of +files having the "_source" suffix). +If all is well, we can tag the packaging work we've just completed: + +{% include jekyll-theme/shell.html cmd='gbp buildpackage --git-tag-only' %} + +This will create the `debian/1.0-1` tag in the repository. + +We are now ready to upload the source package to Launchpad. +It's done using the `dput` tool. +The naive way would fail: + +{% include jekyll-theme/shell.html cmd='dput ppa:john-doe/test ../build-area/test_1.0-1_source.changes' %} + +This is due to the fact that we've specified that we're targetting the +"unstable" distribution in debian/changelog. +There's no "unstable" distribution of Ubuntu though; we need to manually +specify the minimal-supported version (e.g. "bionic"): + +{% include jekyll-theme/shell.html cmd='dput ppa:john-doe/test/ubuntu/bionic ../build-area/test_1.0-1_source.changes' %} + +What about other distributions? +Well, if the binary package doesn't need recompiling, we can use Launchpad's +"Copy packages" feature; this is well-described in this [Ask Ubuntu question]. + +[Ask Ubuntu question]: https://askubuntu.com/q/23227/844205 + +New versions +------------ + +When a new version is released, git-buildpackage helps to integrate it to the +packaging branch. +Let's say the new version is tagged `v1.1`: + +{% include jekyll-theme/shell.html cmd='git checkout debian' %} +{% include jekyll-theme/shell.html cmd='git merge v1.1' %} +{% include jekyll-theme/shell.html cmd='gbp dch' %} + +The above command will update debian/changelog; modify it manually to target +the usual "unstable" distribution instead of "UNRELEASED" and update the +version to something like "1.1-1". + +{% include jekyll-theme/shell.html cmd='git add debian/' %} +{% include jekyll-theme/shell.html cmd='git commit -m \'Debian release 1.1\'' %} +{% include jekyll-theme/shell.html cmd='gbp buildpackage -S' %} + +This will build the source package for the new version in the ../build-area +directory; you can then upload it Launchpad and copy the built binary packages. + +Aftermath +--------- + +This fucking sucks. +What's the way to sanely manage the repository if the build/runtime +dependencies are different for different Ubuntu versions? +I have no idea. +Some pointers to help you understand what's going on in this tutorial more +deeply: + +* [When upstream uses Git: Building Debian Packages with git-buildpackage](https://honk.sigxcpu.org/projects/git-buildpackage/manual-html/gbp.import.upstream-git.html) +* [Using Git for Debian packaging](https://www.eyrie.org/~eagle/notes/debian/git.html) + +Good luck with this, because I'm definitely overwhelmed. diff --git a/_posts/2022-09-07-gdb-sleep-all.md b/_posts/2022-09-07-gdb-sleep-all.md new file mode 100644 index 0000000..a575afd --- /dev/null +++ b/_posts/2022-09-07-gdb-sleep-all.md @@ -0,0 +1,33 @@ +--- +title: Pause all userspace processes +snippets_root_directory: snippets/gdb_sleep_all +snippets_language: bash +snippets: + main: + - gdb_sleep_all.sh + gdb: + - sleep.gdb +--- +If you need to debug some kind of monitoring system (or just have some fun), +you might want to pause all userspace processes for a certain number of seconds +(to measure delays, etc.). + +You can easily do this using GDB like this: + +{% include jekyll-theme/snippets/section.html section_id='main' %} + +sleep.gdb is a very simple GDB script; it basically sleeps for a determined +amount of seconds: + +{% include jekyll-theme/snippets/section.html section_id='gdb' %} + +You can simply run + + sudo ./gdb_sleep_all.sh + +and all of your userspace processes should be frozen for 30 seconds. + +On a couple of servers, this worked quite well; not so well on my laptop with +Xfce installed. +Obviously, this would require a bit of work to adapt for containers as well. +Otherwise, pretty neat, huh? diff --git a/_posts/2022-11-07-ptrace-sigtraps.md b/_posts/2022-11-07-ptrace-sigtraps.md new file mode 100644 index 0000000..2f42a81 --- /dev/null +++ b/_posts/2022-11-07-ptrace-sigtraps.md @@ -0,0 +1,72 @@ +--- +title: 'Fun with ptrace: SIGTRAPs galore' +date: 2022-11-07 13:00 +0100 +--- +When using `PTRACE_ATTACH` the `ptrace` mechanism reuses SIGTRAP for a number +of things by default. +This makes it unnecessarily hard to distinguish regular traps (possibly caused +by breakpoints we might place) from other events. + +1. After `ptrace(PTRACE_SYSCALL)`, syscall-stops will be reported as SIGTRAPs. + + ```c + int status; + + ptrace(PTRACE_SYSCALL, pid, 0, 0); + waitpid(pid, &status, 0); + + if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { + /* We don't know if the tracee has just entered/exited a syscall or + * received a regular SIGTRAP (could be caused by a breakpoint we + * placed). */ + } + ``` + + This is fixed by using the `PTRACE_O_TRACESYSGOOD` option. + + ```c + int status; + + ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD); + ptrace(PTRACE_SYSCALL, pid, 0, 0); + waitpid(pid, &status, 0); + + if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) { + /* We know for sure that the tracee has just entered/exited a + * syscall. */ + } + ``` + +2. Every `execve` call will be reported as a SIGTRAP. + + ```c + int status; + + ptrace(PTRACE_CONT, pid, 0, 0); + waitpid(pid, &status, 0); + + if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { + /* We don't know if the tracee just called execve() or received a + * regular SIGTRAP (could be caused by a breakpoint we placed). */ + } + ``` + + This is fixed by using the `PTRACE_O_TRACEEXEC` option. + + ```c + int status; + + ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXEC); + ptrace(PTRACE_CONT, pid, 0, 0); + waitpid(pid, &status, 0); + + if (WIFSTOPPED(status) && status >> 8 == (SIGTRAP | PTRACE_EVENT_EXEC << 8)) { + /* We know for sure that the tracee has just called execve(). */ + } + ``` + + This point doesn't apply to tracees attached using `PTRACE_SEIZE`. + {: .alert .alert-info } + +As you can see, you should always use at least the `PTRACE_O_TRACESYSGOOD` and +`PTRACE_O_TRACEEXEC` options to be able to distinguish between SIGTRAPs. diff --git a/_posts/2022-11-07-ptrace-waitpid.md b/_posts/2022-11-07-ptrace-waitpid.md new file mode 100644 index 0000000..b5f34fa --- /dev/null +++ b/_posts/2022-11-07-ptrace-waitpid.md @@ -0,0 +1,51 @@ +--- +title: 'Fun with ptrace: a waitpid pitfall' +date: 2022-11-07 12:00 +0100 +--- +When tracing a process using `ptrace`, one often uses the `waitpid` system call +to wait until something happens to the tracee. +It often goes like this (error handling is omitted for brevity): + +```c +/* We have previously attached to tracee `pid`. */ + +int status; + +waitpid(pid, &status, 0); + +if (WIFEXITED(status)) { + /* Tracee has exited. */ +} +if (WIFSIGNALED(status)) { + /* Tracee was killed by a signal. */ +} +/* Tracee was stopped by a signal WSTOPSIG(status). */ +``` + +What if a single thread is attached to multiple tracees? +Then we can use `-1` as the first argument to `waitpid`, and it will wait for +any child to change state. + +```c +int status; +pid_t pid = waitpid(-1, &status, __WALL); +``` + +What's little known, however, is that `waitpid(-1)` will by default consume +status changes from other thread's children. +So if you have two tracer threads A and B, and each of them is attached to a +tracee, then thread A might consume thread B's tracee status change by calling +`waitpid(-1)`. +Therefore, thread A would have thread B's tracee status. +A typical application could be completely unprepared for this scenario. + +To avoid that, use the `__WNOTHREAD` flag. +That way, thread A will only consume status changes from its own children only. + +```c +int status; +pid_t pid = waitpid(-1, &status, __WALL | __WNOTHREAD); +``` + +In my opinion, `__WNOTHREAD` should often be a default in well-structured +applications. diff --git a/_posts/snippets/gdb_sleep_all/gdb_sleep_all.sh b/_posts/snippets/gdb_sleep_all/gdb_sleep_all.sh new file mode 100755 index 0000000..e923740 --- /dev/null +++ b/_posts/snippets/gdb_sleep_all/gdb_sleep_all.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -o errexit -o nounset -o pipefail + +# Select all process IDs that are _not_ children of PID 2, [kthreadd]. +pids="$( ps -o pid --no-headers --ppid 2 -p 2 --deselect )" + +for pid in $pids; do + cmdline="$( cat "/proc/$pid/cmdline" | tr '\0' ' ' )" || continue + echo ------------------------------------------------------------------ + echo "PID: $pid" + echo "Command line: $cmdline" + echo ------------------------------------------------------------------ + gdb -p "$pid" -x sleep.gdb -batch & +done + +wait diff --git a/_posts/snippets/gdb_sleep_all/sleep.gdb b/_posts/snippets/gdb_sleep_all/sleep.gdb new file mode 100644 index 0000000..6b8f268 --- /dev/null +++ b/_posts/snippets/gdb_sleep_all/sleep.gdb @@ -0,0 +1,2 @@ +shell sleep 10 +quit diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/shared.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/shared.hpp new file mode 100644 index 0000000..796ea85 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/shared.hpp @@ -0,0 +1,6 @@ +#pragma once + +inline int shared() { + static int n = 0; + return ++n; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.cpp new file mode 100644 index 0000000..330ba80 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.cpp @@ -0,0 +1,11 @@ +#include "another.hpp" + +#include <iostream> + +inline void shared() { + std::cout << "another.cpp: shared()\n"; +} + +void another() { + shared(); +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.hpp new file mode 100644 index 0000000..9c26d3f --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/another.hpp @@ -0,0 +1,3 @@ +#pragma once + +void another(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/main.cpp new file mode 100644 index 0000000..e278b9f --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/inline/weird/main.cpp @@ -0,0 +1,13 @@ +#include "another.hpp" + +#include <iostream> + +inline void shared() { + std::cout << "main.cpp: shared()\n"; +} + +int main() { + shared(); + another(); + return 0; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.cpp new file mode 100644 index 0000000..f13b3a1 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.cpp @@ -0,0 +1,6 @@ +#include "another.hpp" +#include "shared.hpp" + +void another() { + Test test; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.hpp new file mode 100644 index 0000000..9c26d3f --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/another.hpp @@ -0,0 +1,3 @@ +#pragma once + +void another(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/main.cpp new file mode 100644 index 0000000..b3118c1 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/main.cpp @@ -0,0 +1,8 @@ +#include "another.hpp" +#include "shared.hpp" + +int main() { + Test test; + another(); + return 0; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/shared.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/shared.hpp new file mode 100644 index 0000000..ef4da34 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/separate_method_definitions/shared.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include <iostream> + +struct Test { + Test(); +}; + +inline Test::Test() { + static int x = 0; + std::cout << ++x << '\n'; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/main.cpp new file mode 100644 index 0000000..fde1a43 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/main.cpp @@ -0,0 +1,10 @@ +#include "proxy.hpp" +#include "shared.hpp" + +#include <iostream> + +int main() { + std::cout << shared() << '\n'; + std::cout << proxy() << '\n'; + return 0; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.cpp new file mode 100644 index 0000000..78e4611 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.cpp @@ -0,0 +1,6 @@ +#include "proxy.hpp" +#include "shared.hpp" + +int proxy() { + return shared(); +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.hpp new file mode 100644 index 0000000..7dfc52a --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/proxy.hpp @@ -0,0 +1,3 @@ +#pragma once + +int proxy(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/shared.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/shared.hpp new file mode 100644 index 0000000..647f49e --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static/shared.hpp @@ -0,0 +1,6 @@ +#pragma once + +static int shared() { + static int n = 0; + return ++n; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/main.cpp new file mode 100644 index 0000000..fde1a43 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/main.cpp @@ -0,0 +1,10 @@ +#include "proxy.hpp" +#include "shared.hpp" + +#include <iostream> + +int main() { + std::cout << shared() << '\n'; + std::cout << proxy() << '\n'; + return 0; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.cpp new file mode 100644 index 0000000..78e4611 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.cpp @@ -0,0 +1,6 @@ +#include "proxy.hpp" +#include "shared.hpp" + +int proxy() { + return shared(); +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.hpp new file mode 100644 index 0000000..7dfc52a --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/proxy.hpp @@ -0,0 +1,3 @@ +#pragma once + +int proxy(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/shared.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/shared.hpp new file mode 100644 index 0000000..28de441 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/static_and_inline/shared.hpp @@ -0,0 +1,6 @@ +#pragma once + +static inline int shared() { + static int x = 0; + return ++x; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/main.cpp new file mode 100644 index 0000000..fde1a43 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/main.cpp @@ -0,0 +1,10 @@ +#include "proxy.hpp" +#include "shared.hpp" + +#include <iostream> + +int main() { + std::cout << shared() << '\n'; + std::cout << proxy() << '\n'; + return 0; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.cpp new file mode 100644 index 0000000..78e4611 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.cpp @@ -0,0 +1,6 @@ +#include "proxy.hpp" +#include "shared.hpp" + +int proxy() { + return shared(); +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.hpp new file mode 100644 index 0000000..7dfc52a --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/proxy.hpp @@ -0,0 +1,3 @@ +#pragma once + +int proxy(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/shared.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/shared.hpp new file mode 100644 index 0000000..e21a00c --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespace_and_inline/shared.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace { + +inline int shared() { + static int x = 0; + return ++x; +} + +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.cpp new file mode 100644 index 0000000..cc7556d --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.cpp @@ -0,0 +1,19 @@ +#include "another.hpp" + +#include <iostream> + +namespace { + +struct Test { + Test() { + std::cout << "another.cpp: Test::Test()\n"; + } + + float y = 1.; +}; + +} + +void another() { + Test test; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.hpp new file mode 100644 index 0000000..9c26d3f --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/another.hpp @@ -0,0 +1,3 @@ +#pragma once + +void another(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/main.cpp new file mode 100644 index 0000000..e383ded --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/ok/main.cpp @@ -0,0 +1,22 @@ +#include "another.hpp" + +#include <iostream> + +namespace { + +struct Test { + Test() { + std::cout << "main.cpp: Test::Test()\n"; + } + + int x = 1; +}; + +} + +int main() { + Test test; + std::cout << test.x << '\n'; + another(); + return 0; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.cpp new file mode 100644 index 0000000..0e0bff9 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.cpp @@ -0,0 +1,15 @@ +#include "another.hpp" + +#include <iostream> + +struct Test { + Test() { + std::cout << "another.cpp: Test::Test()\n"; + } + + float y = 1.; +}; + +void another() { + Test test; +} diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.hpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.hpp new file mode 100644 index 0000000..9c26d3f --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/another.hpp @@ -0,0 +1,3 @@ +#pragma once + +void another(); diff --git a/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/main.cpp b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/main.cpp new file mode 100644 index 0000000..abd42b7 --- /dev/null +++ b/_posts/snippets/static_vs_inline_vs_unnamed_namespaces/unnamed_namespaces/weird/main.cpp @@ -0,0 +1,18 @@ +#include "another.hpp" + +#include <iostream> + +struct Test { + Test() { + std::cout << "main.cpp: Test::Test()\n"; + } + + int x = 1; +}; + +int main() { + Test test; + std::cout << test.x << '\n'; + another(); + return 0; +} diff --git a/_posts/snippets/ubuntu_packaging/basic/changelog b/_posts/snippets/ubuntu_packaging/basic/changelog new file mode 100644 index 0000000..6b7131e --- /dev/null +++ b/_posts/snippets/ubuntu_packaging/basic/changelog @@ -0,0 +1,5 @@ +test (1.0-1) unstable; urgency=medium + + * Initial release. + + -- John Doe <John.Doe@example.com> Wed, 10 Mar 2021 16:15:19 +0000 diff --git a/_posts/snippets/ubuntu_packaging/basic/control b/_posts/snippets/ubuntu_packaging/basic/control new file mode 100644 index 0000000..55f8252 --- /dev/null +++ b/_posts/snippets/ubuntu_packaging/basic/control @@ -0,0 +1,13 @@ +Source: test +Section: utils +Priority: optional +Maintainer: John Doe <John.Doe@example.com> +Build-Depends: debhelper-compat (= 12) +Standards-Version: 4.4.1 +Homepage: https://example.com/test + +Package: test +Architecture: all +Depends: ${misc:Depends} +Description: This is a test package. + This is a test package, just trying out Debian packaging. diff --git a/_posts/snippets/ubuntu_packaging/basic/copyright b/_posts/snippets/ubuntu_packaging/basic/copyright new file mode 100644 index 0000000..8a67e52 --- /dev/null +++ b/_posts/snippets/ubuntu_packaging/basic/copyright @@ -0,0 +1,31 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: test +Upstream-Contact: John Doe <John.Doe@example.com> +Source: https://example.com/test + +Files: * +Copyright: 2021 John Doe <John.Doe@example.com> +License: MIT + +Files: debian/* +Copyright: 2021 John Doe <John.Doe@example.com> +License: MIT + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/_posts/snippets/ubuntu_packaging/basic/rules b/_posts/snippets/ubuntu_packaging/basic/rules new file mode 100644 index 0000000..cbe925d --- /dev/null +++ b/_posts/snippets/ubuntu_packaging/basic/rules @@ -0,0 +1,3 @@ +#!/usr/bin/make -f +%: + dh $@ diff --git a/_posts/snippets/ubuntu_packaging/gbp/gbp.conf b/_posts/snippets/ubuntu_packaging/gbp/gbp.conf new file mode 100644 index 0000000..d9f7e5f --- /dev/null +++ b/_posts/snippets/ubuntu_packaging/gbp/gbp.conf @@ -0,0 +1,5 @@ +[DEFAULT] +upstream-tag = v%(version)s +debian-branch = debian +pristine-tar = False +export-dir = ../build-area/ diff --git a/_posts/snippets/ubuntu_packaging/install/test.install b/_posts/snippets/ubuntu_packaging/install/test.install new file mode 100644 index 0000000..6222235 --- /dev/null +++ b/_posts/snippets/ubuntu_packaging/install/test.install @@ -0,0 +1 @@ +test.sh usr/bin diff --git a/all/index.html b/all/index.html new file mode 100644 index 0000000..c0aa6c5 --- /dev/null +++ b/all/index.html @@ -0,0 +1,8 @@ +--- +title: Archive +layout: default +navbar: + link: Archive + priority: 3 +--- +{% include jekyll-theme/categories/all.html %} diff --git a/assets/css/gdb.css b/assets/css/gdb.css new file mode 100644 index 0000000..ac7ee1f --- /dev/null +++ b/assets/css/gdb.css @@ -0,0 +1,5 @@ +td code { + /* Override Bootstrap styling in tables: */ + padding: 0; + background-color: inherit; +} diff --git a/assets/css/guides.css b/assets/css/guides.css new file mode 100644 index 0000000..2b1665c --- /dev/null +++ b/assets/css/guides.css @@ -0,0 +1,21 @@ +.pre_container { + position: relative; +} +.pre_mark { + position: absolute; + top: 5px; + right: 5px; + user-select: none; +} +.pre_do pre { + border-color: #00d100; +} +.pre_do .glyphicon { + color: #00d100; +} +.pre_dont pre { + border-color: red; +} +.pre_dont .glyphicon { + color: red; +} diff --git a/assets/img/ssh_tunnel_services.png b/assets/img/ssh_tunnel_services.png Binary files differnew file mode 100644 index 0000000..163cb24 --- /dev/null +++ b/assets/img/ssh_tunnel_services.png diff --git a/c++/index.html b/c++/index.html new file mode 100644 index 0000000..e2fd103 --- /dev/null +++ b/c++/index.html @@ -0,0 +1,5 @@ +--- +title: C++ +layout: default +--- +{% include jekyll-theme/categories/category.html category=page.title archive_link='/all/' %} diff --git a/common.cmake b/common.cmake deleted file mode 100644 index d719542..0000000 --- a/common.cmake +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright (c) 2017 Egor Tensin <Egor.Tensin@gmail.com> -# It's a CMake code snippet I use in all of my CMake projects. -# It makes targets link the runtime statically by default + strips debug -# symbols in release builds. -# The latest version can be found at -# https://gist.github.com/egor-tensin/cmake-common. -# Distributed under the MIT License. - -# Version: 2017-05-19T13:51:22+00:00 - -get_directory_property(parent_directory PARENT_DIRECTORY) -set(is_root_project $<NOT:parent_directory>) - -set(USE_STATIC_RUNTIME "${is_root_project}" CACHE BOOL "Link the runtime statically") -set(STRIP_SYMBOL_TABLE "${is_root_project}" CACHE BOOL "Strip symbol tables") - -if(is_root_project) - if(MSVC) - add_compile_options(/MP /W4) - elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) - add_compile_options(-Wall -Wextra) - endif() -endif() - -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -function(use_static_runtime_msvc target) - get_target_property(target_type "${target}" TYPE) - if(target_type STREQUAL INTERFACE_LIBRARY) - else() - target_compile_options("${target}" PRIVATE - $<$<CONFIG:Debug>:/MTd> - $<$<NOT:$<CONFIG:Debug>>:/MT>) - endif() -endfunction() - -function(use_static_runtime_gcc target) - get_target_property(target_type "${target}" TYPE) - if(target_type STREQUAL EXECUTABLE) - target_link_libraries("${target}" PRIVATE -static) - endif() -endfunction() - -function(use_static_runtime target) - if(MSVC) - use_static_runtime_msvc("${target}") - elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) - use_static_runtime_gcc("${target}") - else() - message(WARNING "Unrecognized toolset") - endif() -endfunction() - -function(strip_symbol_table_gcc target) - get_target_property(target_type "${target}" TYPE) - set(release_build $<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>>) - if(target_type STREQUAL INTERFACE_LIBRARY) - else() - target_link_libraries("${target}" PRIVATE $<${release_build}:-s>) - endif() -endfunction() - -function(strip_symbol_table target) - if(MSVC) - elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) - strip_symbol_table_gcc("${target}") - else() - message(WARNING "Unrecognized toolset") - endif() -endfunction() - -function(apply_common_settings target) - if(TARGET "${target}") - get_target_property(target_imported "${target}" IMPORTED) - if(target_imported STREQUAL NOTFOUND OR NOT target_imported) - if(STRIP_SYMBOL_TABLE) - strip_symbol_table("${target}") - endif() - if(USE_STATIC_RUNTIME) - use_static_runtime("${target}") - endif() - endif() - endif() -endfunction() - -macro(add_executable target) - _add_executable(${ARGV}) - apply_common_settings("${target}") -endmacro() - -macro(add_library target) - _add_library(${ARGV}) - apply_common_settings("${target}") -endmacro() diff --git a/haskell/index.html b/haskell/index.html new file mode 100644 index 0000000..1fe6653 --- /dev/null +++ b/haskell/index.html @@ -0,0 +1,5 @@ +--- +title: Haskell +layout: default +--- +{% include jekyll-theme/categories/category.html category=page.title archive_link='/all/' %} diff --git a/index.html b/index.html new file mode 100644 index 0000000..ae418c8 --- /dev/null +++ b/index.html @@ -0,0 +1,9 @@ +--- +title: Main page +layout: default +navbar: + link: Blog + priority: 1 + paginated: true +--- +{% include jekyll-theme/posts/feed.html %} diff --git a/makefile_escaping/escaping_quotes.mk b/makefile_escaping/escaping_quotes.mk deleted file mode 100644 index f6d7994..0000000 --- a/makefile_escaping/escaping_quotes.mk +++ /dev/null @@ -1,19 +0,0 @@ -MAKEFLAGS += --no-builtin-rules --no-builtin-variables --warn-undefined-variables -unexport MAKEFLAGS -.DEFAULT_GOAL := all -.DELETE_ON_ERROR: -.SUFFIXES: -SHELL := bash -.SHELLFLAGS := -eu -o pipefail -c - -escape = $(subst ','\'',$(1)) - -test_var := Includes ' quote - -echo_test_var := printf '%s\n' '$(call escape,$(test_var))' -bash_test_var := bash -c '$(call escape,$(echo_test_var))' - -test: - printf '%s\n' '$(call escape,$(test_var))' - bash -c '$(call escape,$(echo_test_var))' - bash -c '$(call escape,$(bash_test_var))' diff --git a/makefile_escaping/escaping_shell.mk b/makefile_escaping/escaping_shell.mk deleted file mode 100644 index 365e6ff..0000000 --- a/makefile_escaping/escaping_shell.mk +++ /dev/null @@ -1,19 +0,0 @@ -MAKEFLAGS += --no-builtin-rules --no-builtin-variables --warn-undefined-variables -unexport MAKEFLAGS -.DEFAULT_GOAL := all -.DELETE_ON_ERROR: -.SUFFIXES: -SHELL := bash -.SHELLFLAGS := -eu -o pipefail -c - -escape = $(subst ','\'',$(1)) - -cwd := $(shell basename -- "$$( pwd )") - -simple_var := Simple value -composite_var := Composite value - $(simple_var) - $(cwd) - -.PHONY: test -test: - @printf '%s\n' '$(call escape,$(cwd))' - @printf '%s\n' '$(call escape,$(composite_var))' diff --git a/makefile_escaping/quoting_args.mk b/makefile_escaping/quoting_args.mk deleted file mode 100644 index 6d7278c..0000000 --- a/makefile_escaping/quoting_args.mk +++ /dev/null @@ -1,16 +0,0 @@ -MAKEFLAGS += --no-builtin-rules --no-builtin-variables --warn-undefined-variables -unexport MAKEFLAGS -.DEFAULT_GOAL := all -.DELETE_ON_ERROR: -.SUFFIXES: -SHELL := bash -.SHELLFLAGS := -eu -o pipefail -c - -test_var := Same line? -export test_var - -test: - @printf '%s\n' $(test_var) - @printf '%s\n' '$(test_var)' - @printf '%s\n' $$test_var - @printf '%s\n' "$$test_var" diff --git a/notes/index.html b/notes/index.html new file mode 100644 index 0000000..ca9b941 --- /dev/null +++ b/notes/index.html @@ -0,0 +1,26 @@ +--- +title: Notes +navbar: + link: Notes + priority: 2 +layout: default +sidebar: + notes: + hide: true +--- +<h1>{% include jekyll-theme/categories/label.html category='Notes' monospace=true %}</h1> +{% if site.notes.size > 0 %} + {% comment %} + Sorting can be done in _config.yml with Jekyll 4.0 or later. + {% endcomment %} + {% assign note_list = site.notes | sort_natural: 'title' %} + {% for note in note_list %} + <a href="{{ note.url | relative_url }}" class="feed-entry"> + {% capture title %}<h5 class="text-monospace">{{ note.title }}</h5>{% endcapture %} + {% capture label %}<p><span class="glyphicon glyphicon-menu-right"></span> <span class="text-monospace">{{ note.subtitle }}</span></p>{% endcapture %} + {% include jekyll-theme/flex-header.html title=title right=label %} + </a> + {% endfor %} +{% else %} + <p>Sorry, no notes have been added yet.</p> +{% endif %} diff --git a/nrvo_by_default/.gitignore b/nrvo_by_default/.gitignore deleted file mode 100644 index 25e9f74..0000000 --- a/nrvo_by_default/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.obj -*.exe diff --git a/nrvo_by_default/Makefile b/nrvo_by_default/Makefile deleted file mode 100644 index 13feb31..0000000 --- a/nrvo_by_default/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -# Builds rvo.cpp on various optimization levels supported by GCC. -# The compiler is `g++` by default. -# You can change the compiler to be used using the CXX variable. -# For example, if you want to compile using `x86_64-w64-mingw32-g++`, run -# -# > make CXX=x86_64-w64-mingw32-g++ - -CXXFLAGS = -Wall -Wextra -DNDEBUG -std=c++11 -static -s - -optimization_levels = 0 1 2 3 - -all:: - -define make_target -rvo.$(CXX).O$(1).exe: rvo.cpp - $(CXX) $(CXXFLAGS) -O$(1) $$< -o $$@ - -all:: rvo.$(CXX).O$(1).exe -endef - -$(foreach i,$(optimization_levels),$(eval $(call make_target,$(i)))) - -.PHONY: clean-all - -clean-all: - $(RM) $(wildcard rvo.$(CXX).*.exe) diff --git a/nrvo_by_default/nmake.mk b/nrvo_by_default/nmake.mk deleted file mode 100644 index 45aefd4..0000000 --- a/nrvo_by_default/nmake.mk +++ /dev/null @@ -1,21 +0,0 @@ -CXXFLAGS = /nologo /W4 /EHsc /MT /DNDEBUG - -all: rvo.cl.Od.exe rvo.cl.O1.exe rvo.cl.O2.exe rvo.cl.Ox.exe - -rvo.cl.Od.exe: rvo.cpp - $(CXX) $(CXXFLAGS) /Od /Fe:$@ $** - -rvo.cl.O1.exe: rvo.cpp - $(CXX) $(CXXFLAGS) /O1 /Fe:$@ $** - -rvo.cl.O2.exe: rvo.cpp - $(CXX) $(CXXFLAGS) /O2 /Fe:$@ $** - -rvo.cl.Ox.exe: rvo.cpp - $(CXX) $(CXXFLAGS) /Ox /Fe:$@ $** - -clean: - del rvo.obj - -clean-all: clean - del rvo.cl.*.exe diff --git a/nrvo_by_default/rvo.cpp b/nrvo_by_default/rvo.cpp deleted file mode 100644 index a1e73e5..0000000 --- a/nrvo_by_default/rvo.cpp +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) 2015 Egor Tensin <Egor.Tensin@gmail.com> -// This file is part of the "Egor's blog" project. -// For details, see https://github.com/egor-tensin/blog. -// Distributed under the MIT License. - -#include <iostream> -#include <utility> - -namespace -{ - class C - { - public: - explicit C() - { - std::cout << "\tC::C()\n"; - } - - C(C&&) noexcept - { - std::cout << "\tC::C(C&&)\n"; - } - - C(const C&) - { - std::cout << "\tC::C(const C&)\n"; - } - - C& operator=(C&&) noexcept - { - std::cout << "\tC::operator=(C&&)\n"; - return *this; - } - - C& operator=(const C&) - { - std::cout << "\tC::operator=(const C&)\n"; - return *this; - } - - ~C() - { - std::cout << "\tC::~C()\n"; - } - }; - - C make_rvo() - { - return C{}; - } - - C make_prevent_rvo() - { - return std::move(C{}); - } - - C make_nrvo() - { - C c; - return c; - } - - C make_prevent_nrvo() - { - C c; - return std::move(c); - } -} - -int main() -{ - { - std::cout << "C c\n"; - C c; - } - { - std::cout << "C c(make_rvo())\n"; - C c(make_rvo()); - } - { - std::cout << "C c{make_rvo()}\n"; - C c{make_rvo()}; - } - { - std::cout << "C c = make_rvo()\n"; - C c = make_rvo(); - } - { - std::cout << "C c(make_nrvo())\n"; - C c(make_nrvo()); - } - { - std::cout << "C c{make_nrvo())\n"; - C c{make_nrvo()}; - } - { - std::cout << "C c = make_nrvo()\n"; - C c{make_nrvo()}; - } - { - std::cout << "C c(make_prevent_rvo())\n"; - C c(make_prevent_rvo()); - } - { - std::cout << "C c{make_prevent_rvo()}\n"; - C c{make_prevent_rvo()}; - } - { - std::cout << "C c = make_prevent_rvo()\n"; - C c = make_prevent_rvo(); - } - { - std::cout << "C c(make_prevent_nrvo())\n"; - C c(make_prevent_nrvo()); - } - { - std::cout << "C c{make_prevent_nrvo()}\n"; - C c{make_prevent_nrvo()}; - } - { - std::cout << "C c = make_prevent_nrvo()\n"; - C c = make_prevent_nrvo(); - } - return 0; -} diff --git a/makefile_escaping/escaping_env_vars.mk b/prelude.mk index 28fc9c6..da836d7 100644 --- a/makefile_escaping/escaping_env_vars.mk +++ b/prelude.mk @@ -1,3 +1,5 @@ +# Please see https://tensin.name/blog/notes/makefile.html + MAKEFLAGS += --no-builtin-rules --no-builtin-variables --warn-undefined-variables unexport MAKEFLAGS .DEFAULT_GOAL := all @@ -19,16 +21,3 @@ ifeq ($$(origin $(1)),command line) override $(1) := $$(value $(1)) endif endef - -simple_var := Simple value - -test_var ?= $(simple_var) in test_var -$(eval $(call noexpand,test_var)) - -simple_var := New simple value -composite_var := Composite value - $(simple_var) - $(test_var) - -.PHONY: test -test: - @printf '%s\n' '$(call escape,$(test_var))' - @printf '%s\n' '$(call escape,$(composite_var))' diff --git a/std_call_once_bug/CMakeLists.txt b/std_call_once_bug/CMakeLists.txt deleted file mode 100644 index 31d45de..0000000 --- a/std_call_once_bug/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -project(std_call_once_bug CXX) - -include(../common.cmake) - -add_executable(sample sample.cpp) diff --git a/std_call_once_bug/README.md b/std_call_once_bug/README.md deleted file mode 100644 index 176dda0..0000000 --- a/std_call_once_bug/README.md +++ /dev/null @@ -1,26 +0,0 @@ -std::call_once bug in Visual C++ 2012/2013 -========================================== - -Code samples from the post "std::call_once bug in Visual C++ 2012/2013". - -Building --------- - -Create the build files using CMake and build using your native build tools -(Visual Studio/make/etc.). - -In the example below, the project directory is "C:\workspace\personal\blog" -and Visual Studio 2013 is used, targeting x86. - - > cmake -G "Visual Studio 12 2013" C:\workspace\personal\blog\std_call_once_bug - ... - - > cmake --build . --config release - ... - -See also --------- - -* [License] - -[License]: ../README.md#license diff --git a/std_call_once_bug/sample.cpp b/std_call_once_bug/sample.cpp deleted file mode 100644 index 01b5092..0000000 --- a/std_call_once_bug/sample.cpp +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2016 Egor Tensin <Egor.Tensin@gmail.com> -// This file is part of the "Egor's blog" project. -// For details, see https://github.com/egor-tensin/blog. -// Distributed under the MIT License. - -#include <ctime> - -#include <chrono> -#include <iostream> -#include <mutex> -#include <sstream> -#include <string> -#include <thread> - -namespace -{ - template <typename Derived> - class Singleton - { - public: - static Derived& get_instance() - { - std::call_once(initialized_flag, &initialize_instance); - return Derived::get_instance_unsafe(); - } - - protected: - Singleton() = default; - ~Singleton() = default; - - static Derived& get_instance_unsafe() - { - static Derived instance; - return instance; - } - - private: - static void initialize_instance() - { - Derived::get_instance_unsafe(); - } - - static std::once_flag initialized_flag; - - Singleton(const Singleton&) = delete; - Singleton& operator=(const Singleton&) = delete; - }; - - template <typename Derived> - std::once_flag Singleton<Derived>::initialized_flag; - - class Logger : public Singleton<Logger> - { - public: - Logger& operator<<(const char*) - { - return *this; - } - - private: - Logger() - { - std::this_thread::sleep_for(std::chrono::seconds{3}); - } - - ~Logger() = default; - - friend class Singleton<Logger>; - }; - - class Duke : public Singleton<Duke> - { - private: - Duke() - { - Logger::get_instance() << "started Duke's initialization"; - std::this_thread::sleep_for(std::chrono::seconds{10}); - Logger::get_instance() << "finishing Duke's initialization"; - } - - ~Duke() = default; - - friend class Singleton<Duke>; - }; - - std::mutex timestamp_mtx; - - std::string get_timestamp() - { - std::lock_guard<std::mutex> lck{timestamp_mtx}; - const auto tt = std::time(NULL); - return std::ctime(&tt); - } - - void entered(const char* f) - { - std::ostringstream oss; - oss << "Entered " << f << " at " << get_timestamp(); - std::cout << oss.str(); - } - - void exiting(const char* f) - { - std::ostringstream oss; - oss << "Exiting " << f << " at " << get_timestamp(); - std::cout << oss.str(); - } - - void get_logger() - { - entered(__FUNCTION__); - Logger::get_instance(); - exiting(__FUNCTION__); - } - - void get_duke() - { - entered(__FUNCTION__); - Duke::get_instance(); - exiting(__FUNCTION__); - } -} - -int main() -{ - std::thread t1{&get_duke}; - std::thread t2{&get_logger}; - t1.join(); - t2.join(); - return 0; -} |