henry_flower: A melancholy wolf (Default)

Іграшка 1-ша: CLI кліент для DW. Користуюсь десь з місяць, ніби працює.

Read more... )

Іграшка 2-га: транслітератор з російської в українську

Read more... )
henry_flower: A melancholy wolf (Default)

https://sigwait.gitlab.io/les_podervyansky--plays/

Коли 2 роки тому, мені закортіло перечитати п'єси ЛП, я не спромігся їх знайти у нормальному epub. Тоді я зробив свою компіляцію і розповів про це десь у LJ. Не знаю чому, але the resulting epub/pdf найбільше скачували з Росії. Навіщо їм там Подерв'янський?

На останній сторінці epub'у я попрохав надсилати мені erratum. За 2 роки це наважилася зробити аж 1 людина з Чехії. Сьогодні я, нарешті, знайшов сили epub обновити.

Read more... )
henry_flower: A melancholy wolf (Default)

Майкрософт вперше додали механізма autounattend.xml за часів захоплюючих краєвидів Вісти. Користувач завантажував те що зараз називається ADK віндюковий, який містив графічну ютіліту WSIM, яка випльовувала xml-файла. Останній клавсь до кореня usb-драйву і віндюка (ув ідеалі) інсталювався без втручання хомо сапієнса.

Нічого революційно нового ув тому не було--лайнакси схожі механізми мали багато років, наприклад kickstart ув Федорі.

Прублема ADK є ув тому, що він потребує інстальованої ОС, а способу генерації autounattend.xml з-під ОС конкурентів Майкрософт не надає, окрім цінних порад писати його ув текстовому редакторі вручну (дуже дякую).

Не слідкуючи за їх цирком на дроті, деякий час назад я почув, що з винахідливим змістом того .xml можна отримати інсталяцію віндюка без сучасних та корисних аплікації типу коупайлота або ноутпеда (модерн) чи аутлука (н'ю), та без необхідності ув танцюванні гопака для створювання 'офлайнового' користувацького акавнту.

Різноманітні онлайнові генератóри autounattend.xml змінюються як пори року. Поточним, працюючим, популярним (ППП) залишається дойчландський unbeaufsichtigter (вимовляйте це самі) generator.

На цьому пригоди лише починаються:

  1. Якщо потрібен .iso з доданим autounattend.xml для створення мошини віртуальної, то як модифікувати оригінальний .iso? Всередені там є хфайлова система udf, яку змаунтувати можна тільки read-only.

  2. Якщо треба інсталювати віндюка на справжнього комп'ютера (ви не повірите, але такі випадки трапляються непоодиноко), то як створити з оригінального .iso завантажувальний usb?

Нагадаю: ми ув лайнаксі, де жодні rufus'и працюють не тут. Різноманітні ютіліти різної ступені занедбаності або підтримують лише .iso лайнаксних дістро, або написані ув найкращому випадку школярами, ув найгіршому--вініпухами чи руснею (маґа-патріот-джон с тексастской області лєнінґрадскоґо района). Ув 2х останніх різновидах є гарний шанс отримати гарного .iso с безоплатним подарунком всередині.

З найсмішніших варіянтів що я бачив, кількість залежностей є така, що аплікацію, разом з залежностями, завертають ув appimage як ковбасу.

.iso → usb

Rufus'и та друзі для режиму UEFI роблять нічого складного: на usb-дівайсі створюється дві GPT партішони:

  1. невелика fat32;
  2. ntfs (рідше--exfat), яка займає решту дівайса.

До 1ї переписують бутлоадера (зазвичай 'універсального', який може вантажити декілька різних ОС). До ntfs копіюють вміст .iso.

Ув нашому варіянті майкрософтський .iso має свого бутлоадера, тому скопіювати можна його. Головний трюка є ув правільному створені партішонів: (1) fat32 не повинен бути з позначками esp та boot, інакше віндюковий інсталятора вирішить що саме на usb-драйві буде EFI розділ для ОС, хоча останню він ставить до геть іншого драйва; (2) без позначки msftdata на ntfs партішоні віндюка кидає "Install driver to show hardware" даялога, а її diskpart малює його як "Unknown". Вкрай неввічливо.

$ parted test.img print | grep -v ^Disk
WARNING: You are not superuser.  Watch out for permissions.
Model:  (file)
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name     Flags
 1      1049kB  1074MB  1073MB  fat32        BOOT     msftdata
 2      1074MB  7516MB  6442MB  ntfs         INSTALL  msftdata

Наївний скрипта, який це втомлююче робить, на жаль, вимагає рута через створення loop devices, але не чіпає хадварних драйвів:

$ sudo ./winiso2img Win11_24H2_English_x64.iso test.img

test.img потім можна перенести на usb-драйв будь-яким улюбленим способом, наприклад:

$ sudo dd if=test.img of=/dev/XXX status=progress bs=1M oflag=direct,sync

Ув принципі, щоб згенерувати хфайла з потрібною таблицею партішонів можна обійтися без losetup+parted, а взяти sfdisk, але як потім змонтувати партішони всередині test.img звичайним користувачем я не знаю.

Модифікація .iso

Майкрософтські .iso зроблені з хфайлової системи udf, а крихітний шар iso9660 там містить лише хфайла README.TXT з повідомленям про skill issue читача.

Лайнакс знає про udf, але маунтить оті .iso лише ув read-only.

Спочатку я хотів взяти 7-zip. Розаркування великого аркайву (> 5GB) займає час навіть на сучасних мошинах, тому бажано повідомляти користувачу що коїться. 7-zip друкує лінію стану розаркування, але робить це ув найворожіший спосіб. По-1ше, автор 7-zip'у вважає що \r (carriage return) то є буржуазноє ізлішєство. Ось фрагмент збереженого аутпуту:

00000000: 2020 304d 2053 6361 6e20 2f68 6f6d 652f    0M Scan /home/
00000010: 616c 6578 2f44 6f77 6e6c 6f61 6473 2f77  alex/Downloads/w
00000020: 696e 2f69 736f 2f77 3131 2f08 0808 0808  in/iso/w11/.....
00000030: 0808 0808 0808 0808 0808 0808 0808 0808  ................
00000040: 0808 0808 0808 0808 0808 0808 0808 0808  ................
00000050: 0808 0808 0808 2020 2020 2020 2020 2020  ......
00000060: 2020 2020 2020 2020 2020 2020 2020 2020
00000070: 2020 2020 2020 2020 2020 2020 2020 2020
00000080: 2008 0808 0808 0808 0808 0808 0808 0808   ...............
00000090: 0808 0808 0808 0808 0808 0808 0808 0808  ................
000000a0: 0808 0808 0808 0808 0808 0808 2020 3025  ............  0%
000000b0: 2031 3030 204f 7065 6e08 0808 0808 0808   100 Open.......
000000c0: 0808 0808 0808 2020                      ......

Воно видаляє рядок за допомогою циклу з \b (backspace), потім друкує пробіли щоб видалити їх знову.

По-2ге, воно відмовляється малювати лінію стану коли stdout не є терміналом. Зі script(1) це можна побороти і, з деяким зусиллям, я змусив аутпут від 7z бути зумісним з dialog(1) gauge, але після проглядання 7-zip src, огида до нього у мене переросла бажання його торкатися. Це 1 з найгірших src, які я бачив. То є диво що воно працює взагалі, а кількість CVE починаючи з 2022 наводить на конспірологічні думки які я тут писати не буду.

Якщо ігнорувати 7-zip, як тоді модифікувати змонтований read-only .iso? Можна скопіювати все ув іншу директорію, але існує більш шляхетний спосіб: overlayfs.

З ним гагібайти хфайлів не копіються, але метод вимагає рута. FreeBSD ніби мала раніше щось схоже (unionfs), але коли я користувався fbsd, воно часто працювало з помилками.

Після отримання директорії з autounattend.xml хфайлом, її завертають ув .iso знову. Скрипта, який це робить, є на 24 рядки більший за попередній winiso2img:

$ wc -l *
  67 mkwiniso
  91 winiso2img
 158 total
$ sudo ./mkwiniso add lol.xml Win11_24H2_English_x64.iso test.iso

Якщо просто треба з розаркованого Майкрософтського .iso створити новий, рута є непотрібний:

$ ./mkwiniso generate dir 1.iso
henry_flower: A melancholy wolf (Default)

712 безглуздих слів про "renewing the Brand".

Дякуючи Зевсу, Гері та іншим богам, ЕйАй, який нищить Stack Exchange, вміє перетворювати воду на сенс:

"Stack Exchange is launching a rebrand because they think their identity is a mess, and it’s hurting their business. Instead of fixing real problems, they’re wasting time and money on consultants and vague “brand strategy” to make everything look more polished while AI eats their lunch. They say they want community input, but this is corporate theater."

henry_flower: A melancholy wolf (Default)

Понапридумували ґолдів:

colorsel screenshot
henry_flower: A melancholy wolf (Default)

З безтурботних часів кінця сивочолого криголама (що мені не подобалось?), для знаходження діректових лінків на .iso, Майкрософт кілька разів змінювали внутрішній ойпіай, загалом, потреба ув якому як була так і залишається незрозумілою, якщо не рахувати необхідність свіжих булітів кожного end-of-year рів'ю.

Щоб завантажити 24h2, треба отримати ойді сесії та список мов, за якими Майкрософт подарує згенерований url ув джейсоні. Як робити найостанніший ріквеста забагато разів на годину (наприклад, нахабно 5 разів), Майкрософт зобанить. Покласти всі .iso на статичного сервера не можна--це буде глузування над здоровий глуздом, потрібна Процедура.

$ grep -Ev '^#|^$' windows-iso-url | wc -l
28

Скрипта вимагає nokogiri.

henry_flower: A melancholy wolf (Default)

Спостерігаючи за бійкою між Поттерінґом та термінальними пуристами, я дізнався про таке собі XTGETTCAP. Це є механізма (відносно новий, ~2019?) для отримання можливостей терміналу без консалтінґу terminfo. Це означає, що брехати про тип терміналу з XTGETTCAP не вийде.

Наприклад, якщо хочеться намалювати користувачу саме монохроматичного інфобокса, зараз можна підсунути назву терміналу, для якого ув terminfo зазначено безбарвне життя:

$ TERM=vt100 dialog --infobox 'ну шо ти малá' 0 0

Це працює тому що даялоґа використовує ncurses, який дивиться ув terminfo. Якби даялоґа стріляв ув термінала XTGETTCAP'апом, замість читання змінної середовища TERM, такий хфокус би не спрацював:

$ echo $TERM
xterm-256color
$ TERM=vt100 ./XTGETTCAP TN
xterm-256color

Якщо ув теорії XTGETTCAP--майбутнє вже сьогодні, то на практиці цей механізма мені не подобається. Можливо in-band signalling було надією людства ув 1970ті, але зараз щоб імплементувати XTGETTCAP запита та прочитати результата, треба перемикати термінала ув raw режим, конвертувати ім'я capability ув hex строку і писати до tty обгорнувши її з \e (ascii 033 ув октал) з обох боків, поряд з іншим мотлохом:

printf '\033P+q%s\033'\\ "$capablity" > "`tty`"

Що для "TN" виглядає як

00000000: 1b50 2b71 3534 3465 1b5c                 .P+q544e.\

Потім треба обережно прочитати результата все ще знаходячись ув raw режимі. Якщо робити це з акуратністю, з якою нарід пише скрипти шелóві, вірогідність того, шо термінал буде заблоковано перманентно, наближається до 1.

Для wayland'у є емулятор терміналу foot, який має окрему C ютіліту для читання XTGETTCAP, але вона наглухо висне, якщо термінала не розуміє запиту. Як можна сподіватися на здібності гіпадріла звичайного, якщо це не можуть порядно зробити навіть люди, які пишуть термінального емулятора.

Окрім xterm (який вимагає увімкненого allowTcapOps, який вирублений по замовчуванню на всіх ОйБіЄм'івських дістро через цирка на дроті), найбільше просунулося у цьому напрямку індійське диво kitty (на яке я маю алергію після стількох років страждань з calibre (автор той самий)), та усілякі ghostly та ін. штукенції, якими користуються 12 людей на планеті земля. iterm2 та діфолтне еплівське одоробло також ніби має підтримку, але мені є лінь перевіряти.

Найсумніше є те, що XTGETTCAP наразі не працює через gnu screen чи tmux, що хоча є вина останніх, популярності XTGETTCAP не додає.

Скрипта, яким можна тестувати:

#!/bin/sh

set -e

eh() { echo "Error: $*" 1>&2; trap - 0; exit 1; }
text2hex() { od -A n -t x1 | tr -d ' \n'; }
hex2text() { sed 's/../0x& /g' | xargs printf '\\\\%03o\n' | xargs printf %b; }
stty_orig=`stty -g`
stty_restore() { stty "$stty_orig"; }

[ -n "$1" ] || eh Usage: XTGETTCAP capability

capablity=`printf '%s' "$1" | text2hex`
trap stty_restore 0

stty -echo raw time 1 min 0
printf '\033P+q%s\033'\\ "$capablity" > /dev/tty
buf=`dd status=none count=1`
stty_restore

[ -n "$buf" ] || eh unsupported terminal
[ "$V" ] && printf %s "$buf" | xxd

buf=`printf %s "$buf" | sed 's/[^a-zA-Z0-9+=]//g'`
[ P1 = "${buf%+*}" ] || eh unknown capabilitah
printf %s "${buf#*=}" | hex2text | xargs

Повинно працювати з пацаватими dash, busybox sh та ін.

hex2text() тут не фонтан, звичайно, але нопешіть як то можна інакше, щоб (а) було сумісно з fbsd, (б) не вимагало чогось ікстернального, як xxd.

$ ./XTGETTCAP TN
xterm-kitty
$ V=1 ./XTGETTCAP colors
00000000: 1b50 312b 7236 3336 6636 6336 6637 3237  .P1+r636f6c6f727
00000010: 333d 3332 3335 3336 1b5c                 3=323536.\
256
$ ./XTGETTCAP шо?
Error: unknown capabilitah
henry_flower: A melancholy wolf (Default)

Ув вікіпідіа написано шо хадварні термінали VT100 (продавалися з 1978 року) та VT220 (1983) були монохромні (кольорові варіянти, напевно, лише для заможних верств гіпадрілів). Terminfo ув лайнаксі про ці термінали містить ту саму інформацію:

$ for i in dumb vt100 vt220 screen xterm $TERM; do printf "%-20s" $i; TERM=$i tput colors; done
dumb                -1
vt100               -1
vt220               -1
screen              8
xterm               8
xterm-256color      256

Кілька днів тому, нєкто Поттерінґ, мастодонів про діфолтне значення TERM, яке обирає systemd:

'The default fallback $TERM we so far picked has been "vt220" … This deemed us to be a good choice, since (unlike the better known vt100) these kind of terminals support PageUp/PageDown keys'

Загалом, ув terminfo дивляться зазвичай тільки ютіліти які для користувацького інтерхфейсу використовують ncurses; решта вгадує можливості терміналу шляхом слідкування за візерунками руху небесних тіл, e.g.:

  • чи є змінна середовища COLORTERM (нещодавно обраний шлях systemd);
  • змінна середовища NO_COLOR (я би віддав перевагу, навпаки, щось на кшталт COLOR=1, бо діфолтне плювання кольорами ув консолі є страшенний несмак);
  • дивитися на TERM і шукати збіг ув своїй вкомпайлений (не жарт) табличці, як то робить ls з coreutils;
  • механізма terminal-colors.d(5) з util-linux, яким користується ніхто, окрім деяких ютіліт з util-linux.

Лайнаксний ls, звичайно, перемагає ув конкурсі найдебільнішого рішення.

Воно друкує барвами для vt100, тому що протягом компіляції coreutils хапається текстовий хфайла з налаштунками для dircolors(1), який має ось такі рядки:

TERM vt100
TERM xterm*

Цей хфайл (його копія зазвичай є ув /etc/DIR_COLORS) скрипта перловий конвертує ув символьного масива

$ head -c7 src/dircolors.hin # ув ріпо coreutils
# Confi
$ (head -c61 && printf 🤡 && tail -c39) < <(src/dcgen src/dircolors.hin)
static char const G_line[] =
{
  '#',' ','C','o','n','f','i',🤡,'r','i','a','b','l','e','s','.',0,
};

який потім інклудиться як dircolors.h ув ls.c, де є хфункція

/* Check if the content of TERM is a valid name in dircolors.  */
static bool known_term_type(void) {
  char const *term = getenv("TERM");
  if (!term || !*term)
    return false;

  char const *line = G_line;
  while (line - G_line < sizeof(G_line)) {
    if (STRNCMP_LIT(line, "TERM ") == 0) {
      if (fnmatch(line + 5, term, 0) == 0)
        return true;
    }
    line += strlen(line) + 1;
  }

  return false;
}

dircolors(1) також парсить нещасну G_line та друкує 0 інструкцій для ls, коли TERM є відсутня чи несе невідоме значення:

$ env -i SHELL=/bin/sh dircolors
LS_COLORS='';
export LS_COLORS

У чому сенс тоді, навіть за відсутності LS_COLORS, розмальовуювати множинні character devices з директоріями (довгий масив color_indicator[] ув ls.c, якщо комусь цікаво), але полишати файли звичайні (кольори для яких G_line має), залишається невідомим.

henry_flower: A melancholy wolf (Default)

Найвизначнішою рисою істот з СНД, навіть якщо останнє вони покинули фізично, залишається гарантована compassion towards victims.

Приклад типовий: https://stas.dreamwidth.org/1468036.html разом з коментарями.

henry_flower: A melancholy wolf (Default)

Бовзер Кроум має інтернáльну сторінку chrome://gpu/, яка малює, поміж інших речей, статуса хадварних відеокодувальників. Якщо драйвер gpu не опиняється ув блеклисті (це слово можна знову використовувати?), ближче до кінця там з'являється таблиця з рядками

Decode hevc main    64x64 to 8192x4352 pixels

З інтернáльною сторінкою пов'язано багато смішного. Наприклад, одного разу на безмежні узбережжя скелястих форумів гоогла викинуло монголку:

"Добрий дєнь! вопрос про страніцу chrome://gpu - у мєня поддєржка ЯндєксКарти запросіла пріслать содєржімоє єтой страніци. Єто что, вообщє? Єто бєзопасно? Я вставіла єє в адрєсную строку і получіла коллапс какой-то - цвєтниє полоси, клінья, сєґмєнти і прочую бєлібєрду, послє чєґо всє откритиє страніци інтєрнєта пошлі "плясать" - содєржімоє пєрєкорєжіло. Вірус, что лі?"

Раніше, якщо треба було дивитися на окремі відеофрейми, брався src відеокодеку на C та відчайдушно компілювався ув вебасемблі. Зараз це ліпше робити ні, т.я. бовзер розуміє вдосталь відеокодеків самотужки і має новітнього ойпіай (йому 5 років) для маніпулювання фреймами--WebCodecs.

Ув лайнаксі, Кроум використовує хадварні відеокодувальникі через завдяки за допомогою VAAPI. Якщо дістро має застарілі (або занадто нові) версії Mesa з їх DRI-драйверами, відеокодувальники ув Кроумі будуть софтварними, навіть якщо GPU підтримує їх хадварну акселерацію. З ідіотськими порадами "не викидайте старі комп'ютери, а інсталюйте на них лайнакса", наївний користувач отримує гул кулерів, який був відсутній ув віндюку, де Кроум дружив з драйверами GPU.

WebCodecs має метода статичного VideoDecoder.isConfigSupported(), яким можна перевірити чи використовує Кроум могутню акселерацію для конкретного кодека. З VideoEncoder це можна перевірити для конкретної роздільної здатності.

Наприклад, якщо пастнути текста нижче ув дівтулзову консоль, воно надрукує трохи більш зручну табличку, аніж простирадла з chrome://gpu/:

Promise.all([
    ['AV1',   'av01.0.08M.10'],
    ['H.264', 'avc1.640033'],
    ['H.265', 'hev1.1.6.L120.90'],
    ['VP8',   'vp8'],
    ['VP9',   'vp09.00.40.08']
].map( async v => ({
    name: v[0],
    codec: v[1],
    support: (await VideoDecoder.isConfigSupported({
        codec: v[1], hardwareAcceleration: "prefer-hardware",
    })).supported
}))).then(console.table)

(Звичайно шо isConfigSupported() є червона хфункція, т.я. потрібно нагадувати гіпадрілам постійно шо життя це є боротьба.)

На жаль, console.table() не вертає html, тому щоб написати уйоб-сторінку, яку зручно відкривати на SBC з ондроїдом або кетайських тівібоксах, треба малювати все самому.

Мінімальний приклад (працює лише ув Кроумі; Файрфокс видає галімат'ю):

$ wc -l *html
47 index.html

є отут (вибачте що не Реакт). Ув якості домашнього завдання, додайте вибір варіянтів baseline/main/high кодеків з <select>.

henry_flower: A melancholy wolf (Default)

Я уявляю це собі так: ув старі добрі часи для швидкого анкетування можна було пнути хропучого сисадміна, який би написав html форму та елементарний сервер (на його улюбленій мові), що з'їдав би post ріквеста і складав результат ув якійсь директорії. Після чого сисадмін вертався до своєї комірки сопти далі, а ікзéк'ютів починав спамити колег адресою 172.20.1.15/surveys/job-satisfaction.html.

Тобто, як то воно була насправді, я гадки не маю. 10+ років тому, коли потрібні були анкети, я робив їх ув гоогл хформах і читав результати ув гоогл шітс. Зараз, звичайно, варіянти коливаються від $0/mo (вся дейта вільно продається кому завгодно) чи $199/mo за "1 active project" з "10K сабмітів" (вся дейта дістається русскім з рансомware угрупувань) до селф-хостинґу з хропучим дівопсом фултайм ув комплекті (вся дейта губиться, коли дівопс звільняється).

Чомусь останній варіянт з селф-хостинґом завжди виглядає як конкурс мошин Руба Голдберґа, хоча такий "проєкта" має бути на рівні можливостей будь-якого школяра. 2 файли ув найпростішому вигляді: form.html та server.js. Ні?

Орхітектура

server.js містить статичного http сервера. На GET /simplest шукає public_html/simplest/index.html та виставляє сесію ув cookie. Автор анкети пише simplest/index.html руками ув текстовому редакторі. Найпростіша анкета:

<form method="post">
Name? <input name="name" type="text" required>
<input type="submit">
</form>

Це є класичний postback, тобто POST ріквест форма відсилає на той самий pathname за яким було GET сторінки. Ув даному випадку це дозволяє з URL викопирсати ім'я анкети. Ув директорії public_html/simplest/ можуть бути будь-які файли, які треба для рендеренгу екстравагантної форми: світлини, відео, джаваскрипт і т.ін.

Число анкет обмежується лише лімітами файлової системи. Додавати анкети можна ув ріалтайм, перезапускати сервера є непотрібно.

Якщо POST був без помилок, application/x-www-form-urlencoded конвертується ув json і ✍️ ув окремій директорії.

Сервер

Бовзерна валідація хформ працює всюди > 10 років (окрім ондроїдного файрфоксу, тому що кожного року треба усім світом збирати мульйони долярів на зарплату CEO і часу на все не вистачає). Перевірку пейлоаду з post ріквеста можна робити загальнопролетарським json schema, який можна генерувати зі статичного form.html на льоту.

Є купа мотлоху який генерує html з json schema, але не навпаки: я знайшов нічого, що оналізує якийсь <input type="text" minlength="2" maxlength="50"> і випльовує скіму, але з ноудівським cheerio можна написати свій генератор за кілька годин. Форми не можуть бути вкладеними одна в одну, тому складність генератора буде рости лише з кількістю віджетів.

Наприклад, з

<input type="radio" name="os" value="linux" required> Linux
<input type="radio" name="os" value="macos"> macOS

генерується

{
  "type": "object",
  "properties": {
    "os": { "enum": [ "linux", "macos" ] }
    ...
  },
  "required": [ "os", ... ]
}

Ванільний фронтенд не був ви фронтендом, якщо би всі стандартні віджети працювали однаково. Якщо для множини radio кнопок підтримується валідація "required", то для множини чекбоксів немає ані "required", ані перевірки мінімальної кількості увімкнених прапорців (навіщо bother, як то кажуть ув Калґарі). Я виставляв data-required="true" та data-min="N" на парентному div.

Коли анкетування є номінально анонімним, auth можна пропустити і просто встановлювати cookie щоб користувач, який згадає шо він цейво, як його, забувсь, шо він хотів написати, міг якийсь час свою анкету редагувати. З

Set-Cookie: sid=2025/01/26/a8b92708-f1dc-4870-abee-907a50a10800; Max-Age=31536000; Path=/js101/
Set-Cookie: signature=3c564fa3f3b534b85fdab82deb575c2ab2af48b7; Max-Age=31536000; Path=/js101/

результати записуються ув db/js101/2025/01/26/XXXX/results.json, які ~легко трансформуються ув csv. Без couchbase, mongo чи mariadb. Жах.

(Здогадайтеся для чого потрібна "signature".)

Анкета стає автоматично 403, якщо хфайл з формою має mtime < зараз. Тобто, валідна анкета має обов'язково мати дату модифікації ув майбутньому, що є трохи незвично, але звільняє від необхідності тримати якогось конфігураційного хфайла.

Якщо над директорією з results.json запустити іншого статичного http сервера, то анкета, сімлінка якої пишеться поряд з results.json, може читати results.json і заповнювати ту саму форму результатами тяжкої роботи користувача.

Віджети

Стандартних має вистачити, але як тільки закортить мінімального дайґрешона, треба готуватися до ведмежих кутів бовзерних ойпіай.

Наприклад, я хотів вертикального слайдера з підписом рисок:

spartaforms-slider

Кастомний уйоб-компонента якого виглядає ось так:

<spartaforms-slider name="knowledge" min="0" max="4" value="0" required>
  <span data-value="4">I'm Douglas Crockford 🧙‍♂️</span>
  <span data-value="3"><i>Très bien</i></span>
  <span data-value="2">Can code with an LLM</span>
  <span data-value="1">Syntax only</span>
  <span data-value="0">Nil</span>
</spartaforms-slider>

Всередині то є звичайний <input type=range>, розвернутий на -90°; зі <span> генерується datalist, з якими слайдер стає схожим на словацького термометра. Можна було би писати datalist самотужки, та його відмовляється рендерити ie6 еплівський вебкіт.

Але <input type=range> сидить ув shadow dom уйоб-компонента, тому форма на сторінці його не бачить! Будь-які зміні ув атрибутах <spartaforms-slider> треба переносити до <input> (і навпаки) самому. Щоб дружити з формами існує спеціяльний ойпіай, який став з ~2023 таким популярним, що про нього користоґо є ~0, окрім скупих речень на whatwg.org.

Якоїсь підтримки ув збереженні інпуту, набраного користувачем ув елементах форми, від бовзера чекати є марно. Доводиться перехоплювати submit івента щоб зберігати їх десь (хоча би ув localStorage) і самотужки відновлювати, коли користувач вирішує ту форму відредагувати. Все це вміють робити різноманітні фреймвоки багато років, а ванільний джаваскрипта полишає нічого, окрім дратування через витрачання часу на цю ґілімат'ю.

Ін конклуз'йоне

Загалом, екперимента визнано невдалим. Що здавалося елементарним 1+N файловим рішенням (де N--кількість анкет), ув реальності з 3ма анкетами виросло на

$ tree --gitignore -I test --dirsfirst
.
├── public_html
│   ├── eng210
│   │   ├── form.css -> ../form.css
│   │   ├── form.js -> ../form.js
│   │   ├── index.html
│   │   └── widgets.js -> ../widgets.js
│   ├── js101
│   │   ├── form.css -> ../form.css
│   │   ├── form.js -> ../form.js
│   │   ├── index.html
│   │   └── widgets.js -> ../widgets.js
│   ├── simplest
│   │   └── index.html
│   ├── 60438.svg
│   ├── favicon.ico
│   ├── form.css
│   ├── form.js
│   ├── index.html
│   ├── posted.html
│   └── widgets.js
├── Makefile
├── mkschema.js
├── package.json
└── server.js

з малонадихаючим розміром джаваскрипта:

$ wc -lc *js public_html/*js
  196  5260 mkschema.js
  249  8322 server.js
  136  4193 public_html/form.js
  101  2906 public_html/widgets.js
  682 20681 total

Приклад анкети є ось тут. Чи має сенс цей ковгосп викладати на ґітгаб, я не знаю.

henry_flower: A melancholy wolf (Default)

Such is human stupidity that whatever is difficult
to obtain is always thought to be better.
-- Peter Martyr d'Anghiera, 1524.

Колись давно, років 8 тому, ув часи сивочолого гетьмана і кволих реформ мусоріату, на HN з'явився лінка на http-сервера розміром 1KB. Не src того сервера був 1KB, а його статично зкомпільований байнарник. Все що той сервер вмів робити це відповідати на будь-який ріквест 1 файлом. Я проявив 0 інтересу, тому що написано воно було на asm/C ув пропорції 60/40.

Ідея такого розміру програм сподобалася французькому автору haproxy і він вмовив нарід ув LKML погодитися закомітити окрему бібліотеку для написання "nolibc" аплікацій. Прикладом такої аплікації є rcutorture--тестування кернела--де генерується невеличкий initrd, написаний на C без використання будь-якої libc.

Іронія ув необхідності спеціяльної бібліотеки щоб не використовувати стандартну бібліотеку нагадує спробу обійти коло, рухаючись по його контуру.

На тему < 1KB байнарників є класичний текста Really Teensy ELF Executables, але у ньому йде мова про асемблера, на який я не бажав звертати уваги ув минулому і на який не бажаю звертати уваги зараз.

Чи можна створювати крихітні (байнарі-вайз) аплікації без асемблеру на C без стандартної бібліотеки? Майже пусту main() лінкер--лінкує, але:

$ cat 42.v1.c
int main() { return 42; }

$ clang -Os 42.v1.c -o 42.v1.o -c
$ ld.lld -e main -o 42.v1 42.v1.o

$ ./42.v1
Segmentation fault (core dumped)

Виявляється, осемблер потрібен щоб кликати сисколи лайнакса:

$ cat 42.v2.c
static inline long syscall(long NR, long arg1) {
  long r;
  __asm__ volatile("syscall" : "=a" (r) : "a" (NR),
                   "D" (arg1) : "rcx", "r11", "memory");
  return r;
}
int main() { return 42; }
void _start() { syscall(60, main()); }

$ clang -Os 42.v2.c -o 42.v2.o -c
$ ld.lld -e _start -o 42.v2 42.v2.o
$ strip -s 42.v2

$ strace ./42.v2
execve("./42.v2", ["./42.v2"], 0x7ffc7190b9a0 /* 105 vars */) = 0
exit(42)                                = ?
+++ exited with 42 +++

Ага! Такий статичний байнарник має розмір 800 байт і залежить від 0 бібліотек. Номер сисколу (60) для exit можна подивитися ув файлі arch/x86/entry/syscalls/syscall_64.tbl кернельного src. На відміну від віндюка, де ABI змінюється з щорічними "feature updates", такий 800-байтовий ікзек'ютабл буде працювати доки є жива орхітектура x86_64.

Чи складно тоді, маючи обгортки для лайнаксних сисколів, написати елементарного TCP сервера для x86_64? Я вирішив спробувати:

$ _out/uptime-tcp-nolibc &
[1] 53161
$ nc --recv-only 127.0.0.1 1377
 07:16:29 up 1 day(s) 22:14,  load average: 0.19, 0.31, 0.30

Воно вертає шось схоже на команду uptime на ремоутній мошині, але не використовує ніяких екстернальних команд чи бібліотек. Якого розміру uptime-tcp-nolibc та скільки він їсть пам'яті написано ув кінці посту.

Якщо без libc немає жодного способу робити I/O чи відкривати сокета, тоді С стає фанкшонал мовою, про яку мріяли вчоні з 80х років, але, на їх невдачу, кернела набув IP стек та аналоги open(2) з write(2).

42.v2.c не вміє читати argv чи environ, а варіятивні хфункції у ньому роблять кордамп. Останне лікується

__attribute__((force_align_arg_pointer))
void _start() { ... }

З argv все є набагато гірше: якщо мета є саме отримувати поінтера на argv, потрібен знову осемблер (~14 рядків), який я тут постити не буду, щоб не лякати людей. Технічно, прочитати argv можна просто відкривши /proc/self/cmdline, ув якому \0 є роздільником аргументів:

$ tr \\0 \\n < /proc/self/cmdline
tr
\0
\n

але вас засміють ув інтервебах.

Перша каменюка на шляху до сокетів лежить ув man-сторінках відповідних libc враперів. Наприклад, bind(2) чекає на аргумента struct sockaddr *, опис якого ув man-сторінці ip(7) був скопійований з документації якоїсь 4.3BSD-Tahoe 1987 року і відповідає реальності нутрощів лайнуксного сьогодення з висоти дзвіниці Києво-Печерської лаври. (Потрібний додатковий паддінґ' unsigned char __pad[8].)

Якщо libc-врапери вертають -1 на помилку та виставляють errno, то сисколи вертають errno × -1. Ув справжніх libc errno є локальна для поточного треду. Для мікімаусного nolibc серверу писати свою версію позікс тредів це як збирати гелікоптер AK1-3 на подвір'ї поміж 2ма хрущовками, тому тут errno--глобальна змінна. Як зберігати інтерхфейса, до якого звикли всі, хто коли-небудь писав мережеві майкросервіси на C, то найпростіший враппер виглядає як

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
  int r = my_syscall(43, sockfd, (long)addr, (long)addrlen, 0, 0, 0);
  if (r < 0) {
    errno = -r;
    r = -1;
  }
  return r;
}

Сигнали

Ув класичних Unix-серверах, якщо вони писалися не для inetd, конкурентна модель є форкова модель, яка вимагає прислуховуватися до SIGCHLD і чекати доки child завершить роботу, інакше після кожного ріквесту буде залишатися зомбі.

Форкова модель була популярна, тому що дозволяла серверу не лякатися сіґфолта, якщо сервер мав бага на який натрапив клайент: зупинявся лише child-невдаха, всі інші продовжували працювати.

Для встановлення колбеку на окремий сигнал лайнакс має свій rt_sigaction сискола. Ув аргументах той має поінтера на struct sigaction з дуже підступним філдом

void (*sa_restorer)(void);

Ув man-сторінках про нього написано ось таке:

'This flag is used by C libraries [тобто, нами] to indicate that the sa_restorer field contains the address of a "signal trampoline".

'… the C library's sigaction(2) wrapper function informs the kernel of the location of the trampoline code by placing its address in the sa_restorer field of the sigaction structure.'

Шматок з мікімаусного врапера:

int sigaction(int sig, const struct sigaction *act, struct sigaction *oact) {
  // ...

  struct kernel_sigaction kact, koact;
  kact.k_sa_handler = act->sa_handler;
  memcpy(&kact.sa_mask, &act->sa_mask, sizeof(sigset_t));
  // ...
  kact.sa_restorer = &restore_rt; // THE MOST IMPORTANT STEP

  int r = my_syscall(13, sig,
                     (long)(act?&kact:NULL), (long)(oact?&koact:NULL),
                     _NSIG/8, 0, 0);
  // ...
}

Звідкіля береться адреса хфункції для kact.sa_restorer? Це є та сама "signal trampoline", про необхідність якої попереджає лайнаксна документація. Звичайно, можна її не виставляти, але тоді жоден з сигналів не буде пійманий і жоден з колбеків не буде виконаний.

Спочатку я наївно написав

void restore_rt() {
  my_syscall(15, 0, 0, 0, 0, 0, 0); // 15 це rt_sigreturn
  __builtin_trap();
}

сигнали перехоплювалися, колбеки виконувалися,

СЄЧЄНОВ

Цей гіпноз корисний дуже,
У хазяйстві вєрно служить,
Злакі мощно прорастають
І надої возрастають!

але після завершення останніх сервер гепався через сіґфолта. На жаль, тут був потрібен осемблер знову:

__asm__(
  ".globl restore_rt\n"
  "restore_rt:\n"
  "    movq $15, %rax\n"
  "    syscall\n"
  "    hlt\n"
);

Чому не працює С-версія я не знаю. Клод ЕйАй вважає вона "may modify registers or the stack layout."

snprintf та gettimeofday

Життя без snprintf--життя варварів.
-- Невідомий філософ.

Ув src кернелу є приклад мінімалістичної vfprintf на 101 рядок, але вона не підтримує floats, які має друкувати uptime. Замість додавання їх підтримки, ліпше пошукати справжню snprintf, яка не використовує malloc. Така є ув pkg Федори (із усіх можливих місць саме тут) і називається stb_sprintf-devel. Це самотній .h файла, який зкомпільовується ув 18168 байт. Жахливе марнування ресурсів, але нічого не вдіяти. Тій хфункції потрібні va_start з друзями, але gcc та clang мають __builtin_va_* ув комплекті:

#define va_start(v,l)   __builtin_va_start(v,l)

Після інкорпорації stb_sprintf життя заграло новими барвами.

Дізнатися час ув секундах з початку буту можна зі сисколом sysinfo, поточний час--зі gettimeofday. Останній провайдить навіть кількість хвилин на захід від Ґрінвічу! Така розкіш.

Фіналє

Розмір нестріпованого статичного байнарника: 21736 байт. (Я до сих пір не можу повірити як це можливо.)

$ make info
wc -lc *.[ch] | while read -r lines bytes file; do \
 elf=-;\
 echo $file | grep -q '\.c$' && elf=`stat -c%s _out/${file%.*}.o`;\
 echo $file $lines $bytes $elf;\
done | column -t -N\ ,LINES,BYTES,ELF
            LINES  BYTES  ELF
main.c      134    3657   5896
signal.c    76     1699   1704
signal.h    113    2649   -
snprintf.c  6      120    18168
snprintf.h  12     274    -
sockets.c   37     896    1728
sockets.h   45     1119   -
u.c         98     2152   4496
u.h         46     1353   -
uptime.c    67     2148   2128
uptime.h    6      79     -
total       640    16146  -

Якби не stb_sprintf, розмір був би < 10KB.

Але це є не найбільша цікавинка. Ось кількість пам'яті, яку їсть такий конкурентний tcp-сервер після відповіді на 100 одночасних ріквестів:

$ sudo ps_mem -p `pgrep -f uptime-tcp-nolibc`
 Private  +   Shared  =  RAM used       Program

 36.0 KiB +   4.5 KiB =  40.5 KiB       uptime-tcp-nolibc
---------------------------------
                         40.5 KiB

Дуже сміюся. Ув сучасному світі, якщо споживання серверу, який робить 0 складного, < 80 _мегабайт_, то це є видатний вчинок і безстрашний прояв волі, за який треба сертифікат хоноровий, медаль та грошовий бонус для походу ув підпільне казино з повіями.

henry_flower: A melancholy wolf (Default)

Пан @vak нещодавно знайшов дотепну заміну fortune(1), де парсячи реддітовського джейсона окремого ком'юніті, можна собі ув терміналі друкувати ~свіжі жарти.

У коментарях там мументально переписали пáйфона на шелл, тому мінімальний скрипта, з доданими очікуваннями на деякі помилки, виглядає ось так:

lolreddit in xterm

Якщо реддіт вертає не зовсім того джейсона, який ми очікували, ми друкуємо нічого. w3m потрібен щоб перетворювати &amp; і друзів на відповідні символи.

Скрипта має 2 недоліки:

  1. джейсона завантажується кожного разу, а реддіт часто вертає 429;
  2. жарти беруться лише з 1го ком'юніті.

Завантажування можна закешити і перевіряти застарілість кешу на кожному запуску. jq вміє самотужки парсити окремо кожний файловий аргумента. Переписати скрипта ліпше за все як мейкфайла, тому шо лише дурень пише на баші те, шо може зробити Мейка (ніхто так не каже):

#!/usr/bin/make -sf

r := showerthoughts CrazyIdeas oneliners
update := 600

wits := /tmp/reddit-wisdom/all.txt
age := $(shell expr `date +%s` - `date -r $(wits) +%s 2>/dev/null || echo 0`)
$(shell [ $(age) -gt $(update) ] && rm -f /tmp/reddit-wisdom/*)

all: $(wits)
    sort -R < $< | head -1 | w3m -dump -T text/html

$(wits): $(patsubst %, /tmp/reddit-wisdom/%.json, $(r))
    jq -r '.data?.children[]?.data | "\"\(.title)\" -- \(.author)" | gsub("\\n"; " ")' $^ > $@

/tmp/reddit-wisdom/%.json:
    mkdir -p $(dir $@)
    echo Fetching r/$*
    curl -sf 'https://www.reddit.com/r/$*/top.json?sort=top&t=week&limit=100' > $@

.DELETE_ON_ERROR:

(Ув fbsd потрібно встановити gmake та відредагувати shebang.) cowsay з lolcat'ом я прибрав. Завантажений джейсона застаріває кожні 600 секунд, але IRL краще виставити 48 годин.

$ ./reddit-wisdom
Fetching r/showerthoughts
Fetching r/CrazyIdeas
Fetching r/oneliners
"We’re constantly playing a tug of war against companies by paying YouTube to
not show ads while they pay YT to show them. " -- SillySlothySlug
$ ./reddit-wisdom
"make a law where everybody gives money to me" -- Due_Intention_2473

$ wc -l < /tmp/reddit-wisdom/all.txt
186

Найскладніше тут знайти слушні ком'юніті, які мають цікаві тайтлс постів. showerthoughts інколи буває NSFW.

henry_flower: A melancholy wolf (Default)

FreeBSD is very compatible with standards such as POSIX.
-- FreeBSD Developer Handbook

На будь-якому лайнаксі з будь-яким sh-похідним шелом, скрипта нижче друкує hello world:

$ cat -n 1.sh
     1  set -e
     2
     3  hello() {
     4      false
     5      echo hello
     6  }
     7
     8  true | hello && echo world

$ sh 1.sh
hello
world

fbsd зі своїм /bin/sh робить потужний кєк на рядку 4:

$ sh 1.sh
$ echo $?
1
$ sh -x 1.sh
+ set -e
+ true
+ hello
+ false
$ uname -rs
FreeBSD 14.1-RELEASE-p5

Їхній sh(1) має ось таке речення ув роздлі про set -e:

"If a shell function is executed and its exit status is explicitly tested, all commands of the function are considered to be tested as well."

Я, звичайно, не є перший хто це випадково помітив: схожі 4 скарги ув їхній баґзилі date back to 1999-2005, всі обнадійливо позначені як FIXED.

Хто і навіщо fbsd використовує для роботи, окрім контори Соні та ображених на Торвальдса русскіх, залишається незрозумілим.

henry_flower: A melancholy wolf (Default)

Іграшка нова: маючи колекцію з NN тисяч хфото, локально описувати на лайнаксі що коїться на кожній світлині без допомоги Епла, Гоогла або распазнай-с-єйай.лох:

$ alias omglol='find -name \*txt | xargs -n50 grep -li'
$ omglol selfie
./10/IMG_20241019_204444.txt
./09/IMG_20240930_082108.txt
./09/IMG_20240930_082118.txt
./07/IMG_20240712_154559.txt
./07/IMG_20240712_154554.txt

кожний .txt файл лежить поряд з .jpg, який було відправлено до ollama.

Або для слайдшоу:

$ omglol selfie | sed s/txt/jpg/ | xargs feh

Опис можна пхати до exif UserComment, звичайно, але я не хотів модифікувати оригінали.

На жаль, з поточним ollama'овським cli прикріпляти світлини немає куди. Замість cli пропонують робити json пост-ріквести до ollama'овського ендпоінту, перед чим кожне зображення треба спочатку base64-конвертувати.

Другий недолік діфолтного cli--неможливість читати stdin. Я не розумію чому стандартний паттерна будь-якого текстового редактора 'селекнутий текста передати до stdin зовнішній ютіліти', є наразі бонусна фіча вчорашніх крипто-бро, сабскріпшона на яку вони залюбки продадуть простодушному користувачу.

Третій недолік--ігнорування ліміту вихідного тексту для деякого інпуту, для якого ніякі поради конструювання підказок не працюють, а num_predict ув api просто обрізає генерацію, що можна зробити без їх допомоги і так. Зі своїм враппером стає можливим підсумувати підсумки, на чому я і зупинився:

$ ./llm llama3.2 'summarize the following:' < VI.txt | ./llm llama3.2 'summarize the following in 40 words using the present tense:' | fmt -w50
Hernán Cortés and his men arrive on Cozumel
after rough seas delay them; they're rejoiced to
see Geronimo de Aguilar, a Christian prisoner
who's fluent in Yucatecan. Cortés sees great
potential for Aguilar as an interpreter and
prepares to proceed with his expedition.

Нещасний llm врапер--58-рядковий шелóвий скрипта (осьо є його svg), куди ув якості домашнього завдання можна додати async спінера.

Зі скриптом та невеликим мейкфайлом, процеса аналізу світлин виглядає як:

$ find . -iname \*.jpg | sed 's/\.[^.]*$/.txt/' | xargs -r -d\\n -n50 llm-image

де llm-image це:

#!/usr/bin/make -f
model := llama3.2-vision
define desc
@printf "%s\n\n" $(model) > $@
echo 'describe the image' | llm -i $< $(model) | tee -a $@
endef
%.txt: %.jpg; $(desc)
%.txt: %.JPG; $(desc)

Фасебооківська llama3.2-vision не є єдина модель, знатна описувати зображення (llava:13b, наприклад, вміє це також і працює ув 5 разів швидше), але вона здалася мені найбільш корисною ув виробництві keywords для пошуку.

henry_flower: A melancholy wolf (Default)

Як нарід робить векторні скріншоти терміналу? Ось такі:

Я гадав є конвертори аутпуту зі script(1) ув SVG, але нічого такого не знайшов. Результат з asciinema нормально конвертується лише ув бітмапові зображення. Мотлох ansi-ту-шото на пáйфоні та расті або видає скуйовджені світлини або не розуміє ті ansi сіквенсес, що відповідають за рух курсору. Покинутим termtosvg можна генерувати купу svg, але він покладається на пáйфонівський in memory емулятора терміналу, який має купу багів.

Сучасний xterm вміє робити "SVG Screen Dump", але з наївним позіціонуванням літер. Наприклад, рядок '$ uname' він серіялізує як:

<g fill='rgb(60.00%, 60.00%, 60.00%)'>
 <text x='2' y='17'>$</text>
 <text x='22' y='17'>u</text>
 <text x='32' y='17'>n</text>
 <text x='42' y='17'>a</text>
 <text x='52' y='17'>m</text>
 <text x='62' y='17'>e</text>
</g>

Це означає, що кернінґа шрифта до уваги не береться і результат виглядає чудернацько.

Сучасний xterm також вміє робити "XHTML Screen Dump" (ув меню Ctrl + Миша-1), який гендериться ув бовзері майже ідеально (з замалим line-height). Додати необхідного стилю до .xhtml файлу ж нескладно, але як конвертнути той xhtml ув svg?

Пассо нумеро уно

Бовзери вміють друкувати ув pdf. Кроум має headless режима, а гооглова бібліотека puppeteer (з дебільним інтерфейсом), має Page#pdf() метода. Нам лише потрібно вибрати розмір в'юпорту та опцію 'друкувати background graphics'.

$ cat html2pdf
#!/usr/bin/env node

import util from 'util'
import fs from 'fs'
import puppeteer from 'puppeteer'

async function pdf(url, output, opt) {
    let bowser = await puppeteer.launch()
    let page = await bowser.newPage()

    let print_settings = Object.assign({
        path: output,
        printBackground: true,
        width: 1920,
        height: 1080,
    }, opt)

    await page.emulateMediaType('screen')
    await page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36')
    try {
        await page.goto(urlise(url), { waitUntil: 'networkidle0' })
        await page.pdf(print_settings)
    } catch (e) {
        err('Error:', e.message)
    }

    await bowser.close()
}

function urlise(s) {
    return (/^https?:/.test(s)) ? s : 'file://' + fs.realpathSync(s)
}

function err(...msg) { console.error(...msg); process.exit(1) }

let args = util.parseArgs({
    options: {
        width: { type: 'string', short: 'w' },
        height: { type: 'string', short: 'h' },
    },
    allowPositionals: true
})

if ( !(args.positionals[0] && args.positionals[1]))
    err('Usage: html2pdf URL output.pdf')

pdf(args.positionals[0], args.positionals[1], args.values)

Скрипта підтримує -w та -h опції і конвертує будь-яку уйоб-сторінку, а не тільки локальні файли. Якщо не виставити user-agent, по-замовчуванню буде HeadlessChrome, на що деякі сайти неприємно реагують.

Пассо нумеро дуе

PDF, на жаль, не буде мати розмір конь тенту, а буде містити borders (як це українською? межа?) Прибрати їх простіше всього Inkscape'ом:

$ inkscape --actions "select-all;fit-canvas-to-selection" 1.pdf -o 2.pdf

Пассо нумеро тре

$ pdf2svg 2.pdf 1.svg

Воно автоматом перетворює текст ув path. Зменшити розмір .svg можна svgcleaner'ом.

На кроці нумеро дуе можна було би зразу конвертнути рельтат ув svg, але він тоді виходить гаргантюажного розміру.

Енівей, фінальний мейкфайла:

$ cat xterm-xhtml-to-svg
#!/usr/bin/make -f

font := monospace

$(if $(and $(i),$(o)),,$(error Usage: xterm-xhtml-to-svg i=1.xhtml o=1.svg))
__dir__ := $(dir $(realpath $(lastword $(MAKEFILE_LIST))))

$(o): $(i).dirty.svg; svgcleaner --quiet $< $@

%.nokogiri.xhtml: $(i)
    ruby -rnokogiri -e 'd = Nokogiri.XML(STDIN.read); d.css("#vt100 > pre").first["style"] = "font-family: '$(call se,$(font))'; line-height: 1.2"; puts d.to_xml' < $< > $@

%.uncropped.pdf: %.nokogiri.xhtml; $(__dir__)/html2pdf $< $@

%.pdf: %.uncropped.pdf
    inkscape --actions "select-all;fit-canvas-to-selection" $< -o $@

%.dirty.svg: %.pdf; pdf2svg $< $@

se = '$(subst ','\'',$1)'
.INTERMEDIATE: $(i).dirty.svg
henry_flower: A melancholy wolf (Default)

Коли рік тому до імаксу додали інтерактивну хфункцію emoji-insert, я подумав що hell froze over. Окремий emoji інпут, звичайно, мають всі ОС багато років. Ув лайнаксі це залежить від середовища стільниці, і якщо ними (середовищами стільниці) не користуватися, то невблаганну залізну хватку прогресу можна не помітити.

Нещодавно на хакір н'юз хтось презентував свого менхетнського проекту: emoji-клавіятуру написану на голому XCB. На що хтось ув коментарях зазначив що то є вражаюче досягнення, але маючи список пар

emoji1    опис1
emoji2    опис2
…

то emoji вставляються за допомогою будь-якого динамічного меню без спеціяльної клавіятури.

Раніше таким меню була аплікація dmenu (яку я колись ламав для інкрементного аналога alt-tab для FVWM), але зараз гіпадріли користуються rofi, який є ув пекеджах будь-якого дістро:

$ ls /etc | rofi -dmenu -i

Одного rofi буде замало, треба механізма вставки тексту, який друкує rofi на stdout, до активного вікна. Ув xorg це вміє робити xdotool:

$ ls /etc | rofi -dmenu -i | xdotool type --file -

Де взяти список пар? Федора має unicode-emoji пекеджа (unicode-data ув Деб'яні) з файлом emoji-test.txt:

Зліва від середника там список codepoints. Наприклад, щоб отримати чорного кота 🐈‍⬛, треба взяти звичайного кота (0x1F408, 🐈), додати ZERO WIDTH JOINER (0x200D) та чорного квадрату (0x2B1B, ⬛).

На жаль, %b та \Uxxxx ув printf є непортабельні:

$ e="printf '%b\n' '\U1F408\U200D\U2B1B'"
$ bash -c "$e"
🐈‍⬛
$ bash -c /bin/"$e"
/bin/printf: missing hexadecimal number in escape
$ dash -c "$e"
\U1F408\U200D\U2B1B
$ busybox sh -c "$e"
\U1F408\U200D\U2B1B
$ zsh -c "$e"
🐈‍⬛
$ ksh -c "$e"
\U1F408\U200D\U2B1B

Але якщо перевести всі hex до int та роздрукувати кожен байт з int ув octal, це буде працювати навіть ув busybox:

$ echo $'\360\237\220\210\342\200\215\342\254\233'
🐈‍⬛

Файл emoji-test.txt містить 3773 придатних для використання emojis. Щоб не генерувати статичні пари ув якомусь my-dull-list.txt, які будуть задавнюватися при кожному оновленні пекеджу, краще написати швидкого шелóвого фільтра.

Look, ma--no execve() after fork()s:

$ cat codepoint.sh
codepoint_to_oct() {
    local dec="`printf %d 0x${1:-0}`"
    local b1 b2 b3 b4

    if [ "$dec" -lt $((0x80)) ]; then    # 1-byte
        b1=$dec
    elif [ "$dec" -lt $((0x800)) ]; then # 2-byte
        b1=$((0xC0 | (dec >> 6)))
        b2=$((0x80 | (dec & 0x3F)))
    elif [ "$dec" -lt $((0x10000)) ]; then
        b1=$((0xE0 | (dec >> 12)))
        b2=$((0x80 | ((dec >> 6) & 0x3F)))
        b3=$((0x80 | (dec & 0x3F)))
    elif [ "$dec" -lt $((0x200000)) ]; then
        b1=$((0xF0 | (dec >> 18)))
        b2=$((0x80 | ((dec >> 12) & 0x3F)))
        b3=$((0x80 | ((dec >> 6) & 0x3F)))
        b4=$((0x80 | (dec & 0x3F)))
    else
        return 1                # out of range
    fi

    printf \\%03o "$b1" $b2 $b3 $b4
}

emoji_print() { printf %b "`for i; do codepoint_to_oct "$i"; done`"; }

Кросиві var assignments ув codepoint_to_oct() написав chatgpt, після чого Гостре Око помітив що генерувати взагалі потрібно нічого, тому що emoji-test.txt має для кожного codepint готовий emoji.

$ cat emoji-list.sh
: "${DB:=/usr/share/unicode/emoji/emoji-test.txt}"

grep '; fully-qualified ' "$DB" \
    | grep -v 'skin tone' \
    | sed -E 's/.+# ([^ ]+) E[^ ]+ (.+)/\1\t\2/'

Як відфільтровувати безцінні варіянти з кольором шкіри, список виходить дещо коротший:

$ ./emoji-list.sh | wc -l
1898
$ ./emoji-list.sh | tail -3
🏴󠁧󠁢󠁥󠁮󠁧󠁿    flag: England
🏴󠁧󠁢󠁳󠁣󠁴󠁿    flag: Scotland
🏴󠁧󠁢󠁷󠁬󠁳󠁿    flag: Wales

Щоб не викидати codepoint.sh, над яким тяжко пітнів чатбот, можна зробити додаткового хфінта: друкувати список не лише emojis, а велику частку utf8:

$ ./unidata-list.sh | grep TELEPHONE
℡    TELEPHONE SIGN
⌕    TELEPHONE RECORDER
☎    BLACK TELEPHONE
☏    WHITE TELEPHONE
✆    TELEPHONE LOCATION SIGN
📞    TELEPHONE RECEIVER
🕻    LEFT HAND TELEPHONE RECEIVER
🕼    TELEPHONE RECEIVER WITH PAGE
🕽    RIGHT HAND TELEPHONE RECEIVER
🕾    WHITE TOUCHTONE TELEPHONE
🕿    BLACK TOUCHTONE TELEPHONE
🖀    TELEPHONE ON TOP OF MODEM

Довжина списку таких пар є 34,856. Джерело є текстовий хфайл UnicodeData.txt з unicode-ucd пекеджу ув Федорі (unicode-data ув Деб'яні):

$ grep TELEPHONE /usr/share/unicode/ucd/UnicodeData.txt | head -3
2121;TELEPHONE SIGN;So;0;ON;<compat> 0054 0045 004C;;;;N;T E L SYMBOL;;;;
2315;TELEPHONE RECORDER;So;0;ON;;;;;N;;;;;
260E;BLACK TELEPHONE;So;0;ON;;;;;N;;;;;

Якщо генерувати списка лінійно, це буде займати кількадесят секунд. Хоча rofi вміє не чекати, а показувати рядки як вони з'являються на його stdin, процес ліпше пришвидшити:

$ cat unidata-list.sh
__dir__=$(dirname "$(readlink -f "$0")")

: "${DB:=/usr/share/unicode/ucd/UnicodeData.txt}"

sed 1,33d "$DB" | while IFS=\; read -r codepoint desc _ _ _ _ _ _ _ _ alt _; do
    [ -n "$alt" ] && [ '<' = "`printf %c "$desc"`" ] && desc=$alt
    printf '%s %s\n' "$codepoint" "$desc"
done | grep -Eav '^.+\s<' \
     | xargs -r -d \\n -n1 -P "`nproc`" "$__dir__/codepoint.sh"

до чатботівского codepoint.sh додати:

…
set -e
codepoint=${1%% *}; [ "$codepoint" ]
char=`emoji_print "$codepoint"`; [ "$char" ]
printf '%s\t%s\n' "$char" "${1#[0-9A-F]* }"

Якщо навіть з такою кенкаренсі все одно доводиться чекати, споглядаючи цифри прогресу ув rofi, рекомендую користуватися нормальними комп'ютерами, а не raspberry pi.

henry_flower: A melancholy wolf (Default)

Намагаючись приготуватися до неминучого колапсу Кроума, згадав шо є бовзер Файрфокс, фундацьоне якого минулого року збирало усім світом на зарплату своєї CEO.

Чому колапсу? Кроум переходить на "новий" API для ікстеншонів (моніфест 3.0), депрікуючи "старий". За винятком Ublock Origin, всі ікстеншони у мене є самописні (99% того що має гооглівський могозин є malware; я не перебільшую). Ublock Origin працювати з "новим" API не збирається, а я маю 0 стимулу та зиску ув портуванні самописного. 1 з ікстеншонів використовує той самий API що і Ublock Origin, але для додавання/редагування http headers.

З цим ікстеншоном були 2 смішні гішторії. Його статистика плаває від 30 користувачів до раптом 900, 1200, 1900 і знову вниз до 30, а т.я. ніякої телеметрії там немає, чому їм починають користуватися на короткий час ув Азії, я не знаю. Якось після такого бурсту активності мені прийшов листа від якогось кетайця, який чемно запропонував продати йому ікстеншона за €600 (я не відповів).

Іншого разу до мене приколупася гамериканська коб'єта, у якої не працювало замовлення з якогось локального склепу, тому що її бовзер видаляв referer і вона якимось незбагненним чином знайшла того ікстеншона та вкрай наполегливо у мене розпитувала як її referer повернути взад. Я чесно намагався їй допомогти, але ув кінці здався і порекомендував встановити Файрфокса, на що вона відповіла "Foxfire [sic] works, thank you!", а я перехрестився.

Чому колапсу Кроума? Я, звичайно, трохи перебільшую, але маю сумніви, що після стількох років з інтервебом без реклами, ореал ойті та їх сімей добровільно погодиться на, та буде сумлінно спостерігати, наприклад, рекламу ув ютубі. Скільки це додасть мульйонів користувачів ФФ це є цікаве питання, але я очікую невеличкого ренесансу.

Як справи ув ФФ на лайнаксі?

Для свого кроуму він використовує gtk3, який фундацьйоне Ґноума та контора Червоний Капелюх вважає депрікейтед. (Для тих хто живе під скелею та є не ув уйоб-дівелопмент дискурсі: кроум--специфічний крос-бовзерський термін, який означає будь-які елементи gui, які не стосуються DOM'у уйоб-сторінки. Наприклад, меню з букмарками або url-бар. Відповідно, кроумлес є вікно лише з уйоб-сторінкою. Фулскрін режим F11 є прикладом кроумлес.)

Gtk3 означає 0 покращень для кроуму, т.я. дівелопмент тулкіту багато років переїхав на gtk4 та майбутнього gtk5. Старий gtk3 бажають викинути геть, але наразі через аплікації типу ФФ це зробити не вдається, хоча люди намагаються.

Наприклад, одного дня ФФ перестав показувати pointer курсор (використовується при наведенні на лінка). Раніше мені було все одно--я пускав ФФ лише для перевірки рендеренґу. Виявляється це сталосі тому, що ув діфолтний gtk3 темі загубили той курсор для xorg. Хтось накричав на Ґноум, але їм було по-барабану, тому ФФ спеціяльно додав widget.gtk.legacy-cursors.enabled конфіґураційного параметра, який по-замовчуванню є false--доля лайнаксоїдів не ув wayland є страждати.

Але найгірша ситуація є зі шрифтами. Gtk3 підтримує fontconfig і ФФ'шний кроум рендерить заміни згідно користувацьких ~/.config/fontconfig/fonts.conf та налаштувань ув ~/.config/gtk-3.0/, як того юзер очікує. Механізма рендеренґу шрифтів ув бовзерному двигуні ФФ технічно питає fontconfig також, але за своїми правилами, тому на практиці результати відрізняються від Гоогл Кроуму та Майкрософтського Edge.

За часів w2k/winxp на fbsd/лайнакс копіювали віндюкові шрифти Arial, Times New Roman, Courier New, &c (така колекція називалася core fonts), і МС деякий проміжок часу дозволяла їх завантажувати легально. Копіювали всі: вільними лайнаксними шрифтами можна було катувати.

Деб'ян має core fonts ув своїх пекеджах (Федора--ні), але ті шрифти застигли ув версіях чи то w98 чи то w2k і відповідних версіях юнікоду. За цей час з'явилося вдосталь metric-compatible families і технічно навіть оновлена колекція core fonts стала непотрібна.

З /usr/share/fontconfig/conf.avail/30-metric-aliases.conf:

Microsoft Liberation Google CrOS core StarOffice
Arial Liberation Sans Arimo Albany
Arial Narrow Liberation Sans Narrow
Times New Roman Liberation Serif Tinos Thorndale
Courier New Liberation Mono Cousine Cumberland
Cambria Caladea
Calibri Carlito
Symbol SymbolNeu

Але я звик до core fonts. Не оригінальних, а свіжих, які я регулярно оновлював коли оновлювався віндюк. Ув fonts.conf достатньо було мати на кшталт

<alias>
  <family>sans-serif</family>
  <prefer><family>Arial</family></prefer>
</alias>

і гоогловський Кроум рендерував sans-serif Arial'ом замість лайнаксового Liberation Sans, Nimbus Sans чи DejaVu Sans.

Звісно з ФФ це не працює (навіщо). Той наполегливо ставить 1й ліпший sans-serif шрифта, який fontconfig знаходить ув /etc/fonts/conf.d/. Якщо того шрифта дісейблнути ув fonts.conf, ФФ бере наступного. На їхній баґзилі відкритий баґ висить 10й рік.

Подолати це можна тільки створивши ув /etc/fonts/conf.d/ хфайла з ім'ям, яке буде сортуватися (in numeric order) до хфайлів з "family substitution", наприклад, 12-msfonts.conf:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "../fonts.dtd">
<fontconfig>

  <alias>
    <family>sans-serif</family>
    <prefer><family>Arial</family></prefer>
  </alias>

  <alias>
    <family>Arial</family>
    <default><family>sans-serif</family></default>
  </alias>

…
</fontconfig>

Це було 1не з 1ших речей, що було, з 1го погляду, безнадійно зломано на ФФ у порівнянні зі Кроумом. Як то буде далі, прогнозувати не хочу, можливо є крихітний шанс що гоогл передумає з моніфестом 3.0 і переїжджати на ФФ не доведеться (шансів немає).

henry_flower: A melancholy wolf (Default)

Сміявсь

ОйБіЕм:

'In 1964, for example, one of our branch managers staged a burlesque show at a sales conference in the Midwest.

The skit was too vulgar to believe--about an Indian village, starring the manager himself as the chief and some scantily clad models as squaws. They even had live chickens running around on the stage. At the end of the skit the manager disappeared with one of these models into a tepee, and as he turned to go into the tent, the audience saw a sign on his back that said something like "Branch Manager: I do all things for all people." Then he and the girl pulled the tepee flap closed behind them.

There were families present, and somebody who witnessed the show wrote me a letter saying, "Is this what you call IBM dignity?" So I started a big inquiry.'

Father, Son & Co.: My Life at IBM and Beyond Томаса Ватсона молодшого.)

henry_flower: A melancholy wolf (Default)

Якщо набрати link checker ув гооглі, то виявиться що перевірка посилань є серйозний бізнес. Запускання скрипта, який пишуть на 1му курсі, коштує від $13 до $159 на місяць. За $13 дозволяється це робити кожні 2 тижні, а за $159 с барскоґо плєча до 1 разу на день.

Для тих хто готовий нарешті "стати професіоналом", контора зовсім іншого гатунку пропонує вдосконалені та ентерпрайзні рішення за $449 та $1249 на місяць відповідно. Тоді відкриваються потужні можливості відправляння csv-звіту до "колеги чи фрілансера".

Є статичний уйоб-сайта з 375ма сторінками, який має 241 ікстернальний лінка. Зазвичай подібні сайти перевіряють локально на рівні .md хфайлів, з яких він генерується, і ютіліти, які це роблять, працюють доволі швидко, т.я. їм не треба повзати по http зі сторінки до сторінки, створюючи графа. Про них ми говорити не будемо.

Щоб перевірити задіплойеного сайта, а не директорію з .md-хфайлами, додається прублема рекурсивного знаходження посилань та питання коли зупинятися. Спочатку, я вирішив пошукати щось готове ув пекеджах Федори: якийсь пáйфонівський скрипта linkchecker, який за майже 4 хв на локалхості і рівнем рекурсії 20, знайшов 60 лінків загалом, замість 241.

Потім я подивився на W3C'шний link-checker (категорія програм, що є сповнена різноманітністю імен). Окрім уйоб інтерхфейсу він має CLI компонента--перл-скрипта, який витратив 13 хв для народження лоґу з 27859 рядками, де посеред машино-нечитабельного мотлоху пропонується шукати зламані посилання. Можливо, як за таке чарджити клаентів по $1249, то це має сенс; найняти когось буде нескладно.

Ґітхаб є повний "fast" та "async" чекерів, найпопулярнійший з яких (1.9K зірочок) не підтримує рекурсії, але друкує затишні 🔍, ✅, 🚫, 💤 емоджі ув консолі та рожеву progress bar. Рисою професіоналізму посеред сучасних лікн-чекерів є порада використовувати їх з докер-контейнеру, тому що для відправляння http ріквестів наш час вимагає >= 216 бібліотек на будь-якій мові пограмування.

Чи складно написати свій, наприклад, на bash? Хоча він буде мати міцний вайб нульових, коли завантажена сторінка якимось wget майже відповідала сторінці яку рендерив бовзер, для перевірки сторінок, наприклад, документації, такий чекер все одно буде залишатися корисним. Назвемо його badlinks:

$ wc -l lib.bash url* badlinks
  21 lib.bash
  28 urlcheck
  14 urlextract.rb
  54 urlextract-recursive
  24 badlinks
 141 total

На чистому bash це зробити майже неможливо: переписування nokogiri на шелі я залишаю комусь іншому, а ліпшого парсера кострубатого html, аніж nokogiri, не існує ув природі.

badlinks складається з 3 компонентів:

  1. парсера html, що друкує всі посилання з нього (не рекурсивно);
  2. рекурсивного crawler'а, який знаходить посилання з конь тент тайпом text/html, та занурюється до них; на кожному етапі він друкує знайдені leafs;
  3. скрипта, який читає список посилань від п.2 та перевіряє їх валідність.

Парсінґ html

$ curl -sL google.com | ./urlextract.rb q:
https://www.google.com/imghp?hl=uk&tab=wi
http://maps.google.com.ua/maps?hl=uk&tab=wl
…
q:/intl/uk/about.html

q: тут означає будь-яку base, відносно якої друкуються релатівні посилання.

$ cat urlextract.rb
#!/usr/bin/env -S ruby -rnokogiri -raddressable/uri

include Addressable

def eh = abort "Usage: #{$0} base_url < html"
base = URI.parse $*[0] rescue eh
eh unless base&.scheme

Nokogiri::HTML(STDIN.read).css('links,a,img,iframe,script,video,audio').each {|n|
  u = URI.parse(n['src'] || n['href']) rescue next
  next if !u || (!u.scheme && u.path.strip == '')
  u.fragment = nil
  puts u.scheme ? u : base.join(u)
}

Щоб воно працювало, треба сказати gem install nokogiri addressable. Вбудований ув stdlib парсер url є не такий гнучкий як addressable.

Фрагмент (частина рядка після #) видаляється для простоти, хоча, звичайно, можна було би намагатися шукати його (фрагмент) потім ув html.

Філософське питання, яке може виникнути: чи має ютіліта друкувати унікальні лінки лише (тому що уйоб-сторінка може містити кілька геть однакових), чи залишати процес фільтрування downstream, так би мовити, процесам.

Crawler

… є найбільш неприємним компонентом. Шелóві мовні конструкції допомагають погано, і найпримітивніший пошук ув глибину з пародією на детектора циклів, це є все що вдалося втиснути ув 54 рядка коду.

./urlextract-recursive http://127.0.0.1/
0 Scanning http://127.0.0.1/ .
1 Scanning http://127.0.0.1/foo.html http://127.0.0.1/
2 Scanning http://127.0.0.1/bar.png http://127.0.0.1/foo.html
2 External http://example.com http://127.0.0.1/foo.html
1 Leaf http://127.0.0.1/baz.jpg http://127.0.0.1/
…

Алгоритма є вкрай простий:

  1. З отриманого стартового url автоматично дізнаємося про origin. Це можна зробити башом як це було продемонстровано ув пості HTTP get на баші за допомогою страхітливого regexp'у. Origin потрібен для розрізняння посилань на наш уйоб-сайт від посилань на ікстернальні ресурси.
  1. Для кожного лінка зі стартового url робимо HTTP HEAD ріквест. Якщо останній є text/html, занурюємося в, якщо ні--друкуємо його як leaf.

    Формат друку:

     level type url parent

    Щоб не сканувати лінка декілька разів, записуємо його ув хфайл history (1 лінк на рядок для макс швидкості grep'у). Рівень занурення регулюється користувачем і по замовчуванню == 20.

    parent потрібен лише для ютіліти ув наступному розділі.

$ cat urlextract-recursive
#!/usr/bin/env bash

set -e -o pipefail
__dir__=$(dirname "$(readlink -f "$0")")
. "$__dir__/lib.bash"

jn() { jobs -l | wc -l; }

scan() {
    local url="$1" level="$2" parent="$3"

    [ "$level" -ge "$LEVEL" ] && return 0
    echo "$level Scanning $url $parent"

    level=$((level+1))
    for u in `fetch "$url" | "$__dir__/urlextract.rb" "$url" | sort -u`; do
        search "$u" "$history" && continue
        echo "$u" >> "$history"

        if echo "$u" | grep -F -- "${URL[host]}" >/dev/null 2>&1; then
            if is_html "$u"; then
                local async=
                [ 1 == $level ] && [ "$JOBS" -gt 1 ] \
                    && [ `jn` -lt "$JOBS" ] && async=1
                if [ $async ] ; then
                    scan "$u" $level "$url" &
                else
                    scan "$u" $level "$url"
                fi
            else
                echo "$level Leaf $u $url"
            fi
        else
            echo "$level External $u" "$url"
        fi
    done
}

type curl > /dev/null
: "${LEVEL:=20}"
: "${JOBS:=`nproc`}"
url=${1:?Usage [LEVEL=$LEVEL] $0 url}

url_parse "$url"
history=${HISTORY:-`mktemp /tmp/urlextract-recursive.XXXXXX`}
trap 'rm -f $history; exit 130' 1 2 15
trap 'rm -f $history' 0

if is_html "$url"; then
    scan "$url" 0 .
    wait
else
    echo "0 Leaf $url ."
fi

(Вибачте за простирадло.)

Скрипта намагається робити конкурентні ріквести. Найнеприємніше тут є неможливість знати заздалегідь скільки посилань буде далі і складність створення фіксованої кількості worker'ів, перевірка яких була би можлива ув child процесах. Тому "паралелізм" використовується лише на рівні 1. Це означає якщо стартовий url мав рівно 1 text/html посилання, поралелізму не буде.

Якщо не робити ліміта на конкурентні ріквести, вбити мошину можна дуже легко. Я так тестував безлімітний варіянт з 6K gnu libstdc++ doxygen сторінками на локалхості: стався еквівалента форк бімби, лайнакс грохнув ікси та почав люто плюватися сторінками вбитих процесів.

Енівей, отримані рядки від urlextract-recursive легко фільтруються. Наприклад, щоб дізнатися який був макс рівень рекурсії і чи не треба його збільшити:

$ ./urllextract-recursive http://127.0.0.1/ | tee 1
$ awk '{print $1}' 1 | sort -un | tail -1
11

або показати лише ікстернальні посилання:

$ awk '$2 == "External"' 1

Нецікаві шматки з lib.bash:

fetch() { curl -sfL --connect-timeout 3 -m 3 -A 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36' -H 'accept-language: en-US,en;q=0.9' -H 'upgrade-insecure-requests: 1' "$@"; }

is_html() { fetch -I "$1" | grep -Ei '^content-type:\s+text/html' > /dev/null; }

search() { grep -Fx -- "$1" "$2" > /dev/null; }

Перевірка посилань

На відміну від попередньої ютіліти, ця чудово і елементарно паралелізується. Сам скрипта про це гадки не має, тому що оперує лише з 1 url, але конкурентна перевірка робиться ув фінальному врапері наступного розділу через xargs.

$ cat urlcheck
#!/usr/bin/env bash

set -e -o pipefail
__dir__=$(dirname "$(readlink -f "$0")")
. "$__dir__/lib.bash"

check_link() {
    local ec=0
    fetch "$1" | head -c1 > /dev/null || {
        ec=$?
        [ $ec == 23 ] && return 0 # pipe was closed by 'head'
        [ $ec -lt 100 ] && echo "Bad $1 in $2"
        return 101
    }
}

type curl > /dev/null
url=${1:?Usage: IGNORE=domains.txt $0 url parent}
parent=${2:-.}

url_parse "$url" || { echo "Invalid $url in $parent"; exit 100; }

if [ "$IGNORE" ] && search "${URL[host]}" "$IGNORE" ; then
    echo "Ignoring $url in $parent"
else
    [ 2 == $? ] && exit 100
    check_link "$url" "$parent"
fi

Єдина оптимізація гідна уваги це є check_link(), який читає лише 1 байт успішного ріспонзу і перевіряє статусного кода curl.

Деякі уйоб-сайти ітервебу (наприклад wsj, або припизджена квора) вважають curl ботом, ув незалежності від user agent, тому якщо вказати ув IGNORE env var шлях до хфайлу зі списком доменів, посилання з ними перевірятися не будуть.

Врапер

Фінальна обгортка:

$ cat badlinks
#!/usr/bin/env bash

set -e -o pipefail
__dir__=$(dirname "$(readlink -f "$0")")

usage="Usage: $0 [-l max level] [-j max jobs] [-e] [-i domains.txt] url"
link_type=Leaf
while getopts i:l:j:e opt; do
    case $opt in
        i) opt_i=$OPTARG ;;
        l) opt_l=$OPTARG ;;
        j) opt_j=$OPTARG ;;
        e) link_type="$link_type|External" ;;
        *) echo "$usage" 1>&2; exit 1
    esac
done
shift $((OPTIND-1))

: "${1:?$usage}"
export LEVEL=${opt_l:-20} JOBS=${opt_j:-`nproc`} IGNORE=${opt_i}

"$__dir__/urlextract-recursive" "$1" | \
    awk '$2 ~ /'"$link_type"'/ {print $3, $4}' | \
    xargs -r -n2 -P "$JOBS" "$__dir__/urlcheck"

Зі статичним уйоб-сайтом, на який попередньо знайдені готові пекеджи витрачали від 4 до 13 хв, набір скриптів шелóвих впорався за 16 секунд, відсканувавши 769 лінка.

henry_flower: A melancholy wolf (Default)

Ґноум втратив директора:

"Holly Million was named the GNOME Foundation Executive Director in late October. Part of her focus was going to be on helping the GNOME Foundation attain more funding. But coming today as a surprise, she will be departing her role with GNOME effective the end of July. She is stepping down on the basis of pursuing a PhD in psychology and dedicating herself to her own private practice."

Намагання знайти фінансування для Ґімпу викликає такий рівень психоемоційного стресу, який лікується лише отриманням PhD ув психології.

До погодження стати директоркою Ґноума, вона працювала "професійним шаманом, артистом, виробником трав'яних ліків та займалася мікро-гомстедингом". Останнє є не сексуальне збочення, а тип сільського господарства ув Гамериці.

"I do energy healing work on behalf of individuals, land, houses, and businesses.", розказувала про себе пані Мілліон, "I can do this work remotely because everything is connected. We live in a quantum universe."

Гомстединг був дуже ув нагоді: вирощування лікарських трав допомагало "with my shamanic work". Якщо у вас врожай з'їдали комахи, пані Мілліон проводила цілющі церемонії та ритуали по телефону: $250 за годину енергетичної роботи 1:1 з вирівнюванням чакри, або $500 за повне очищення енергії у вашому домі чи охфісі.

Наразі рада директорів Фундацьйоне Ґноума, запаливши пахощі та свічки, шукає нового директора.

henry_flower: A melancholy wolf (Default)

Як хутко довести дівопсу або хмарному провайдеру що завантаження з серверу є повільне? Можна заміряти час загальний, чи копіювати все що curl написав на stderr, або вислати кєно зроблене на телефоні (вертикальне, тремтячою рукою, під кутом > 10°, на відстані > 1м від монітора).

Не пам'ятаю ув якій версії віндюка його іксплорер почав малювати графіка під час копіювання хфайлів (Vista?), але він (віндюк) так і не почав цього робити ані ув класичних своїх бовзерах минулого, ані ув своєму поточному безглуздому різновиді кроума для блінка.

Найнахабнішим повідомленням дівопсу був би svg:

та/чи рядки

00:00:01 1000k
00:00:02 2000k
00:00:03 2500k

ув якості доказу.

2й варяінт є найпростіший; curl, наприклад, друкує

fprintf(tool_stderr,
        "\r"
        "%-3s " /* percent downloaded */
        "%-3s " /* percent uploaded */
        "%s " /* Dled */
        "%s " /* Uled */
        "%5" CURL_FORMAT_CURL_OFF_T " " /* Xfers */
        "%5" CURL_FORMAT_CURL_OFF_T " " /* Live */
        " %s "  /* Total time */
        "%s "  /* Current time */
        "%s "  /* Time left */
        "%s "  /* Speed */
        "%5s" /* final newline */,
        …

кожну секунду, тому лише замінивши \r на \n можна відправляти повного логу:

$ curl http://example.com/1.zip -o 1.zip 2>&1 | tr \\r \\n

та вимагати сатисфакції.

Щоб намалювати графіка з gnuplot, нам потрібні Current time та Speed. Останнє доведеться конвертувати з 1234k чи 4567M ув байти. Спочатку я думав малоелегантно замінювати суфікси:

awk 'function expr(s) {
  h["k"]=2**10; h["M"]=2**20; h["G"]=2**30; h["T"]=2**40; h["P"]=2**50
  for (k in h) sub(k, "*"h[k], s); return s
}
/[0-9.][kMGTP]?$/ { print expr($NF) | "bc"; close("bc") }'

але, виявляється, саме для цього coreutils має ютіліту numfmt(1), про яку я ніколи не чув (додали недавно, лише 12 років тому, не встигаєш слідкувати за всіма ціма новими штуками):

$ echo lol 10M | numfmt --from iec --field 2
lol 10485760

Так, звичайно, зараз не роблять, а кожного разу ретельно пишуть нудні, багатослівні простирадла на пáйфоні. Те що має фітнутися ув 5 рядків шелових макс + 6 рядків скрипта gnuplot'у, потрібно ретельно розмазувати на 70+, ніби працюєш на ойбіем ув році 1984 і тобі платять за кількість рядків коду.

Повна версія іграшкового візуалайзатора пускається як

$ ./curlbench http://example.com/1.zip | gnuplot --persist

де gnuplot дозволяє зберегти графіка ув потрібному форматі.

$ cat curlbench
#!/usr/bin/env -S stdbuf -o0 bash

set -e -o pipefail
numfmt=`type -p gnumfmt numfmt;:`; test "${numfmt:?}"

cat <<E
set xdata time
set timefmt "%H:%M:%S"
set xlabel "Time"
set format y "%.0s%cB"
set grid
plot "-" using 1:2 with lines title ""
E

curl "$@" -fL -o /dev/null 2>&1 | tr \\r \\n | awk '
/[0-9.][kMGTP]?$/ {
  time = index($10, ":") == 0 ? $11 : $10
  if (time != "--:--:--") print time, $NF
}' | tr k K | $numfmt --from iec --field 2

Має працювати також на fbsd та маку (як були встановлені coreutils), але мені є лінь перевіряти.

henry_flower: A melancholy wolf (Default)

Побачив ув твіторі:

Robert @RobertZeltinsh · Jun 6

Діди самодєлкіни, викликаю вас. Є задача, зібрати якусь шляпу щоб коли вмикається і пропадає світло, вона відправляла про це смс.
Знайшов якісь вумні розетки з gsm, але воно криве і косе. Все що з розумним будинком не підходе, бо інтернет відсутній.

З будь-яким непотрібним ондроїдом з встановленим Termux можна написати нескладного скрипта, який буде використовувати 2 ютіліти:

  • termux-battery-status
  • termux-sms-send

(apt-get install termux-api, який вимагає також окрему ондроїдну аплікацію termux-api.)

1ша вертає json:

{
  "health": "GOOD",
  "percentage": 42,
  "plugged": "PLUGGED_AC",
  "status": "CHARGING",
  "temperature": 31.0,
  "current": -564452
}

2га відправляє рядок на телехфонний номер. Ув якості тесту я надіслав sms собі, після чого Київстар життєрадісно відповів що це коштувало мені 3 гривені (~7 євроцентів). Останній раз я таке робив, напевно, ув 2010 році, тому з подивом дізнався, що sms не тільки не є безплатні, а продаються пакетами по 300 штук.

Це означає, що якщо ув гаражі станеться перевантаження ланцюга і світло почне мерехтіти, за годину можна зайняти 1ше місце у Європі за кількістю відправлених sms та сертифікат від Київстару 'ідіот року'.

Отже скрипта має пам'ятати про sms ліміт на день.

Найпростіша скіма з опитуванням батареї може виглядати так: телехфон вмикається до розетки, на телехфоні запускається termux, ув якому запускається:

#!/usr/bin/env bash

set -e
poll_time=3
battery=${battery:-termux-battery-status}
sms=${sms:-termux-sms-send}

...

type jq $battery $sms > /dev/null
number=${1:?no phone number}

last=
while true; do
    status=`$battery | jq -r .plugged`; [ "$status" ]
    [ "$status" = "$last" ] && continue

    [ "$last" = "" ] || case $status in
        UNPLUGGED) send No electricity ;;
        PLUGGED_AC) send A keen and shared excitement ;;
        *) log "Unknown status: $status"
    esac

    last=$status
    sleep $poll_time
done

де poll_time ліпше виставляти не 3 секунди, а ув залежності від ліміту sms, наприклад для 100 штук на день--864 секунди (опитування кожні 14.4 хв).

Найнудніша частина є ув відстеженні кількості відісланих sms. Ви знали що bash вміє порівнювати рядки лексикоґрафічно? Я не знав!

$ [ 2024-06-11 \> 2024-06-10 ] ; echo $?
0
$ [ 2024-06-11 \> 2024-06-12 ] ; echo $?
1

Тоді перевіряючи mtime хфайла, де записується кількість sms, можна скидати ліміта:

sms_limit_file="${XDG_CACHE_HOME:-$HOME/.cache}/elektrokharchuvannia"
sms_limit=10

sms_limit_get() {
    local r=$sms_limit
    [ -r "$sms_limit_file" ] && {
        r=`head -c4 "$sms_limit_file" | awk '{print $0+0 == $0 ? $0 : 0}'`
        local today="`date +%Y-%m-%d`"
        local mtime="$(date -d "@$(stat -c %Y "$sms_limit_file")" +%Y-%m-%d)"
        [ "$today" \> "$mtime" ] && r=$sms_limit
    }
    echo "$r"
}

sms_limit_decr() {
    mkdir -p "`dirname "$sms_limit_file"`"
    echo $((`sms_limit_get` - 1)) > "$sms_limit_file"
}

log() { printf '%s: %s\n' "`date`" "$*"; }

send() {
    [ "`sms_limit_get`" -lt 1 ] && { log SMS dayly limit reached; return; }
    local msg="$* (SMS left: $((`sms_limit_get` - 1)))"
    log "$msg"; log "$msg" | xargs -d\\n $sms -n "$number";
    sms_limit_decr
}

Тестування цього ковгоспу без відсилання повідомлень:

$ sms=true ./elektrokharchuvannia +380148800000

Тут воно повинно показувати нічого. Далі, висмикування дроту з телехфону та вставляння його знову, друкує:

Wed 12 Jun 01:25:15 EEST 2024: No electricity (SMS left: 2)
Wed 12 Jun 01:25:20 EEST 2024: A keen and shared excitement (SMS left: 1)
Wed 12 Jun 01:25:25 EEST 2024: No electricity (SMS left: 0)
Wed 12 Jun 01:25:28 EEST 2024: SMS dayly limit reached
henry_flower: A melancholy wolf (Default)

Коли фасебоок іноді попереджує про непозбувану бентегу, якщо клікнути на ікстернальний лінка (як вам не соромно, you're going to a link outside facebook), то минулого літа ув багатьох газетах від цього відбувався розрив дупи (Facebook is now treating content links like malware).

jwz колись розповідав, що оригінальний Mosaic був піонером цього епроачу, маючи налаштування, з яким бовзер малював попереджувального даялога, коли нещасний користувач клікав на будь-якому лінку.

Виявляється, Mosaic 2.7 можна зібрати на сучасному лайнаксі, що я зробив щоб даялога побачити. Це motif аплікація! Вікна зі налаштуванням там немає, тому що вся конфіґурація очікується ув X resources, якщо хтось пам'ятає цей навіки забутий механізма користувацьких параметрів:

$ grep Mosaic ~/.Xdefaults
Mosaic*protectMeFromMyself: true
Mosaic*XmDialogShell*fontList: -*-helvetica-medium-r-*-*-24-*-*-*-*-*-*-*

Були часи. Щоб змінити розмір шрифта, інакше розгледіти діфолтний на сучасних екранах можна лише за допомогою лупи, довелося передивлятися дерево мотіфних віджетів за допомогою editres.

Mosaic protectMeFromMyself dialog

Salacious materials, так.

henry_flower: A melancholy wolf (Default)

Кожного разу як я перевіряю чи буде працювати лайнаксна аплікація на відюку, то жалкую що спробував.

Наприклад, є ноудний майкро-сервіс foo, який від ОС вимагає лише підтримку TCP/IP, робить жодних змін ув файловій системі, вимагає 0 суто лайнаксних API, пише logs до stdout, вважає що його будуть байнднути до умовного 12345 порту, що привілей спеціяльних не вимагає.

На лайнаксі для такого майкро-сервісу пишуть елементарного юніт-хфайлу ув ~/.config/systemd/user/foo.ini і Боб є ваш дядько--користувач лайнаксу (регулярний) контролює foo за допомогою звичних команд systemctl.

Ув віндюку, починаючи з 10 (чи 8?), з'явилися "per-user" сервіси, наприклад cbdhsvc (це так, у якості конспірації, зашифровується словосполучення сервіс кліпбоарду), які, незважаючи на назву, вимагають привілеї адміністраторські для свого створення і затишні поради "інсталюйте нові ваші аплікації до $env:APPDATA" зіштовхуються зі суворою реальністю майкрософтського ідіотизму.

Чи складно тоді сгенерувати інсталятора, що

  1. перепише директорію з хфайлами майкро-сервісу до $env:PROGRAMFILES/foo;
  2. створить ув реґістрі key з описом сервісу;
  3. створить на десктопі ув паблік фолдері (тобто для всіх користувачів) шортката на http://127.0.0.1:12345?

Нескладно, але я би поставив оцінку 3/10: не рекомендую.

До появи UWP аплікації, які у нас час роблять для майкрософтського магазину істоти що народилися лузерами, генерувався .msi хфайл. Більше 10 років тому для цього був wix toolset--декілька феноменально огидних CLI-утіліт, написаних людьми, для яких поняття смак є так само близьке, як відстань сузір'я Андромеди до планети Земля.

З того часу змінилося нічого. Є інші способи створювати інсталятори, наприклад Inno Setup (якщо вас не дратує даялекта паскалю (так), на якому доводиться писати шматки скриптів для будь-якої нетривіяльної дії), або InstallShield, якщо ви міцно поїхали головою та бажаєте сабскріпшона $2474/рік годувати якихось індусів.

Питати про wix будь-яку LLM--гаяти час. Гоогл забитий порадами для версії 3.x, яка малює даялоги для моніторів 2006 року з 96 dpi. Нова версія 5 має неймовірне поліпшення: вміє створювати список файлів сама, без допомоги ікстернального препроцесора або (я вибачаюсь) xslt! Для цього грандіозного leap forward їм знадобилося 20 років. А також:

  • "інторнет" шортката, який генерує wix, потім неможливо відредагувати (до permissions це стосунку не має), url є вшитий намертво;

  • якщо воно не може стартувати сервіса ув процесі інсталяції--настає павза та rollback, жодні твікі з vital=no, wait=no і т.і. не допомагають, хоча згідно пародії на документацію повинні;

  • інтерхфейсьні лейбли можуть стискатися ув залежності від різолюшену монітору та dpi--дуже зручно якщо ти повідомляєш користувачу "дивись logs ув $env:SystemRoot/Temp/foo*, а згенерований даялог малює .../foo*.

Куди правильно писати logs для користувача NT Authority\Local Service я так і не зрозумів. Від блискучих за точністю порад що він має limited write access to the file system у мене бажання перекваліфікуватися на ілектришона та до кінця своїх днів лагодити розетки ув субурбії Калґарі.

henry_flower: A melancholy wolf (Default)

Хтось пам'ятає Kingpin?

Цього року вийшов рімейка, але кажуть що суттєво гірший за оригінала, який я ностальгічно спробував запустити на w7 vm, але безуспішно.

Іграшка вийшла ув 1999, коли у мене була riva tnt2, яку я влітку того року вициганював у батьків (успішно). Віндюка 98 для Kingpin неможливо встановити на хайпервайзорах сучасних cpu; є патчі на гітхабі від якогось божевільного, хоча на amd вони не працюють.

Але є такий собі 86box. Ніколи про нього не чув! Воно емулює (вибачте) ПіСі від 8088 до pentium 2 з voodoo3 і вміє робити приємне crisp nearest neighbor скейлінґа (замість огидного linear як ув qemu та vmware).

Коли встановився w98, я пустив сльозу споглядаючи GUI. Такої краси зараз не роблять. З деяким зусиллям поборов бажання встановити туди Visual Studio 6 и писати вам тут shareware html-редактора на C++ з MFC.

Цієї неділі пройшов 1й рівень, поїхавши на мацациклі зі Skidrow; далі не хочеться і дратує що немає ріалтаймової мапи.

henry_flower: A melancholy wolf (Default)

Побачив ув твіторі:

✙ цар криса
@retraut

блять, немає curl думав зараз завантажу. А як завантажити коли немає curl, wget?

Це про деб'янвського контейнера. В останніх є зазвичай баш, його ставить debootstrap навіть ув мінімальній конфігурації.

Як відкрити TCP сокета, якщо немає ніяких Рубі інстальованих, жодного компайлера, контейнера є пустий та порожній, і темрява над лайнаксом, і демон ssh ширяє над поверхнею контейнера без форми?

Якщо баш був зкомпайлений з --enable-net-redirections, він вміє оперувати з файлами /dev/tcp/example.com/80:

$ exec 3<> /dev/tcp/www.gnu.org/80
$ printf "%s\r\n" 'HEAD /robots.txt HTTP/1.1' >&3
$ printf "%s\r\n\r\n" 'Host: www.gnu.org' >&3
$ cat <&3
HTTP/1.1 200 OK
Date: Sun, 11 Feb 2024 07:02:40 GMT
Server: Apache/2.4.29
Content-Type: text/plain
Content-Language: non-html
…

Все це є вкрай цікаво, але позбавлене сенсу: сучасний інтервеб, а не уйоб сайти Столмана, розмовляє лише через TLS. Завантажити статичного байнарника curl з ґітхабу так не вийде.

Але можна на нормальному сервері запустити форвардний TLS проксі і під'єднуватися до нього. Ув мінімальному вигляді це може бути

$ ncat -vk -l 10.10.10.10 1234 -e proxy.sh

де proxy.sh:

#!/bin/sh
read -r host
openssl s_client -connect "$host":443 -quiet -no_ign_eof

Баш-клайент відправляє ім'я хоста, який ми на нашому проксі-сервері зчитуємо і за допомогою openssl під'єднуємося до нього. ncat тут працює ув галантному стилі inetd--форкає себе і під'єднує сокета башу до stdin/stdout скрипта proxy.sh, про що було ув цьому бложику на початку року.

Зі сторони баша-клайента залишається декілька питань:

  1. будь-який файл з "releases" сторінок ґітхабу мандрує через 302 рідайректа, тому щоб щось завантажити, треба робити 2 ріквести;

  2. як парсити відповіді? Як мінімум треба знаходити \r\n\r\n, який відділяє headers від омріяних байтів curl.tar.xz; це означає передивлятися байнарний стрім, що є напрочуд неприємно робити ув шелі;

  3. як парсити URL? це є необхідно для реакції на 302.

Почнемо з останнього.

В ідеалі, хотілося би мати хфункцію ув баші, яка би працювала як джаваскриптова URL():

$ node -pe 'new URL("https://q.example.com:8080/foo?q=1&w=2#lol")'
URL {
  href: 'https://q.example.com:8080/foo?q=1&w=2#lol',
  origin: 'https://q.example.com:8080',
  protocol: 'https:',
  username: '',
  password: '',
  host: 'q.example.com:8080',
  hostname: 'q.example.com',
  port: '8080',
  pathname: '/foo',
  search: '?q=1&w=2',
  searchParams: URLSearchParams { 'q' => '1', 'w' => '2' },
  hash: '#lol'
}

На SO є багато відповідей з прикладом ріґекспів, але жоден з них не пройшов у мене тестів з URL вище. Ріґекспи там такої довжини і такі страхітливі, що фіксити їх я не намагався, а спитав чатаджипіті, який зробив лише гірше, і гооглівського джéменай, який з Nї спроби його ніби полагодив.

$ cat lib.bash
declare -A URL

url_parse() {
    local pattern='^(([^:/?#]+):)?(//((([^:/?#]+)@)?([^:/?#]+)(:([0-9]+))?))?(/([^?#]*))?(\?([^#]*))?(#(.*))?'
    [[ "$1" =~ $pattern ]] && [ "${BASH_REMATCH[2]}" ] && [ "${BASH_REMATCH[4]}" ] || return 1
    URL=(
        [proto]=${BASH_REMATCH[2]}
        [host]=${BASH_REMATCH[4]}
        [hostname]=${BASH_REMATCH[7]}
        [port]=${BASH_REMATCH[9]}
        [pathname]=${BASH_REMATCH[10]:-/}
        [search]=${BASH_REMATCH[12]}
        [hash]=${BASH_REMATCH[14]}
    )
}

Пошук \r\n\r\n. Лайнаксний grep має опції -a -b -o, які разом друкують byte offset знайденого патерну. Busybox'сний grep про -ab не знає. Нарід каже про od(1), але без прикладів. Якщо їм друкувати файла як

0000000 68
0000001 20
0000002 3a
…

де лівий стовпчик то є offset, то можна 1ші 32КБ ріспонзу такого формату конвертнути в 1 рядок і шукати \r\n\r\n grep'ом:

offset_calc() {
    od -N $((32*1024)) -t x1 -Ad -w1 -v "$tmp" | tr '\n' ' ' | \
        grep -o '....... 0d ....... 0a ....... 0d ....... 0a' | \
        head -1 | cut -d' ' -f7 || printf -- -1
}

Успішна відповідь там буде 0000345, що вимагає потім видаляння leading zeroes.

Повна версія баш-клайенту, яка підтримує лише https та спочатку зберігає кожну відповідь ув темпоральному файлі, а результат друкує на stdout:

#!/usr/bin/env bash

set -e -o pipefail
. "$(dirname "$(readlink -f "$0")")/lib.bash"

tmp=`mktemp fetch.XXXXXX`
trap 'rm -f $tmp' 0 1 2 15
eh() { echo "$*" 1>&2; exit 2; }

[ $# = 3 ] || eh Usage: fetch.bash proxy_host proxy_port url
proxy_host=$1
proxy_port=$2
url=$3

get() {
    url_parse "$1"; [ "${URL[proto]}" = https ] || return 2

    exec 3<> "/dev/tcp/$proxy_host/$proxy_port" || return 2
    echo "${URL[hostname]}" >&3
    printf "GET %s HTTP/1.1\r\n" "${URL[pathname]}${URL[search]}${URL[hash]}" >&3
    printf '%s: %s\r\n' Host "${URL[hostname]}" Connection close >&3
    printf '\r\n' >&3
    cat <&3
}

get "$url" > "$tmp" || eh ':('

offset_calc() {
    if strings "`command -v sh`" | grep busybox >/dev/null; then
        od -N $((32*1024)) -t x1 -Ad -w1 -v "$tmp" | tr '\n' ' ' | \
            grep -o '....... 0d ....... 0a ....... 0d ....... 0a' | \
            awk '{if (NR==1) print $7+0}'
    else
        grep -aobx $'\r' "$tmp" | head -1 | tr -d '\r\n:' | \
            xargs -r expr 1 +
    fi || echo -1
}
offset=`offset_calc`
headers() { head -c "$offset" "$tmp" | tr -d '\r'; }
hdr() { headers | grep -m1 -i "^$1:" | cut -d' ' -f2; }

status=`head -1 "$tmp" | cut -d' ' -f2`
case "$status" in
    200) [ "$offset" = -1 ] && offset=-2 # invalid responce, dump all
         tail -c+$((offset + 2)) "$tmp" ;;
    302)
        headers 1>&2; echo 1>&2
        hdr location | xargs "$0" "$1" "$2" ;;
    *) headers 1>&2; exit 1
esac

Коли воно бачить 302, то викалупує Location: і рекурсивно кличе самого себе з новою URL. Має працювати навіть на вбогому Alpine Linux.

$ grep PRETTY /etc/os-release
PRETTY_NAME="Alpine Linux v3.19"
$ ./fetch.bash 192.168.11.109 1234 https://github.com/stunnel/static-curl/releases/download/8.6.0/curl-linux-arm64-8.6.0.tar.xz > curl.tar.xz
HTTP/1.1 302 Found
Location: https://objects.githubusercontent.com/…
…

$ file curl.tar.xz
curl.tar.xz: XZ compressed data, checksum CRC64
$ tar xf curl.tar.xz
$ file curl
curl: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, stripped

xcron

Feb. 8th, 2024 18:21
henry_flower: A melancholy wolf (Default)

Знайшов dump $HOME 2008 року, який був зроблений на fbsd. Всередині побачив файла з назвою xcron. Це шелóвий скрипта, який я ув 2008 (і раніше) пускав для нагадування дій як:

% xcron 4 кофе стынет

де через 4 хвилини воно малювало X11 даялога з відповідним повідомленням.

Скрипта є настільки смішний, що заслуговує на зображення тут. Яка це була ітерація його, на жаль, залишається невідомим--про ґіта я тоді не чув, а тримати ув svn різноманітний мотлох було ліньки, тому що створювати ріпо ув svn то була Процедура. (Не пам'ятаю чи існували тоді аналоги ґітхабу для приватних ріпос; якщо і так, то грошей на них все одно не було.)

Біхолд:

xcron script from 2008

Найкращій блока--"cat all arguments", ггг

Не знаю чи може він сперечатися з шедеврами на кшталт

return (foo == "bar" ? true : false)

але мені здається що on a par з ними, так би мовити.

Скрипта, звичайно, є непридатний до сучасний лайнаксів: xmessage то була Xaw ютіліта (кінця 80х?) для створювання елементарних даялогів yes/no. Ув 2008 році користувалися нею майже ніхто, а вірогідність того що вона буде інстальована зараз є 0.

З графічними аналогами dialog(1) за цей час ліпше також не стало, їх популярність залишається дуже низька. Tk помер, байдінґами до gtk/qt ув ruby/python користуються 10 людей на планеті Земля.

Можна було би кликати термінального емулятора:

 $ xterm -fs 20 -e 'whiptail --msgbox KURWA\ BÓBR 0 0'

але по-1ше, xterm буває тільки у поціновувачів середовищ вільних від середовищ стільниці (страшно далєкі оні от народа), по-2ге, портабельного поняття діфолтний термінала не існує--ув кожному середовищі стільниці він є свій зі своїм механізмом з'ясування який саме є улюбленцем користувача. Можна тримати таблицю "éкзек'ютабл аргументи-для-зміни-шрифта", але це є забагато клопоту задля такої дрібниці.

Або:

  • ембедити маленьку C gtk4 програму (яка малює даялога) та компілювати її кожного разу перед закінченням таймера;

  • переписати скрипта на расті з використанням якогось GUI тулкіта типу Slint (який лінкується статично) та розповсюджувати байнарника;

  • писати повідомлення ув $(mktemp /tmp/XXXXXX.html) та кликати бовзера через xdg-open;

  • через D-Bus кликати метода org.freedesktop.Notifications.Notify на віддаленому об'єкті демона нотіфікацій. Кожне середовище стільниці останнього має своє, але API є стандартизований.

Варіянт 2024 з ді-басом:

#!/bin/sh

set -e
eh() { echo "$*" 1>&2; exit 1; }

command -v notify-send >/dev/null || eh no notify-send
[ $# -ge 2 ] || eh "Usage: xcron N msg ..."
sec=$1; [ "$sec" -ge 0 ] 2>&- || eh "Invalid int: $sec"; shift
summary=`ngettext "1 minute is up" "$sec minutes are up" "$sec"`

(
    sleep $((60*sec))
    notify-send -i modem -- "xcron: $summary" "$*"
) &
henry_flower: A melancholy wolf (Default)

Шо тут відбувається? (Підказка: ніякого кроскомпайлінґу)

$ make
cc -std=c17 -Wall -Wextra -Wpedantic -O2    flperc.c   -o flperc
$ readelf -h flperc | grep Machine
  Machine:                           Advanced Micro Devices X86-64

$ rm flperc
$ nspawn-run ~/vm/systemd/debian64-aarch64 make
cc -std=c17 -Wall -Wextra -Wpedantic -O2    flperc.c   -o flperc
$ readelf -h flperc | grep Machine
  Machine:                           AArch64

nspawn-run--невеликий врапер (21 LOC шелóвих) над лайнаксним systemd-nspawn, який з'явився того ж року що і докер (2013), але не став популярним.

Врапер на федорі пускає systemd-nspawn над контейнером--директорією з aarch64 деб'яновським дістро і тоді

  • байндить $HOME до контейнера, в якому заздалегідь було

    • створено користувача з таким самим ім'ям/uid/gid як на федорі;
    • встановлено gcc/make/&c.
  • перед виконуванням make, змінює директорію ув контейнері на таку саму як поточна ув федорі.

Все виглядає так, ніби $HOME був на окремому драйві, який переставили до іншої воркстейшон (яка має CPU іншої архітектури). Там запустили make, потім зе драйва повернули на місце.

(Нового тут є 0, все це давно роблять з докером, але треба же чимось виділятися.)

aarch64 gcc ув контейнері працює на федорі x86_64 через

$ cat /proc/sys/fs/binfmt_misc/qemu-aarch64
enabled
interpreter /usr/bin/qemu-aarch64-static
flags: OCF
offset 0
magic 7f454c460201010000000000000000000200b700
mask ffffffffffffff00fffffffffffffffffeffffff

Я донедавна на qemu-*-static не звертав уваги, аж поки випадково не виконав aarch64 éкзек'ютабл на федорі (рєзультатом чєґо бил нєобичайно удівльон). Загалом, binfmt_misc мені ніколи не подобався з точки зору безпеки, але задля перетворювання на льоту інструкцій aarch64→x86_64, такі записи binfmt_misc вирішив не видаляти.

Команда

$ nspawn-run -b ~/vm/systemd/debian64-aarch64

запустить контейнера як псевдо-віртуальну мошину, яка всередені має systemd з (я вибачаюся) підом 1, але яка з федори виглядає ось так:

На відміну від стародавнього chroot'у, процеси хоста воно не бачить. Хост навпаки, бачить геть усе, а systemd навіть вміє оперувати над таким контейнером:

$ machinectl
MACHINE          CLASS     SERVICE        OS     VERSION ADDRESSES
debian64-aarch64 container systemd-nspawn debian 12      -

$ sudo systemctl -M debian64-aarch64
...
$ sudo journalctl -M debian64-aarch64
...

Шкода що qemu-aarch64-static підтримує не всі лайнаксні сісколи, і деякі речі не працюють взагалі:

$ nspawn-run ~/vm/systemd/debian64-aarch64 strace -e t=uname arch
/usr/bin/strace: test_ptrace_get_syscall_info: PTRACE_TRACEME: Function not implemented
/usr/bin/strace: ptrace(PTRACE_TRACEME, ...): Function not implemented
/usr/bin/strace: PTRACE_SETOPTIONS: Function not implemented
/usr/bin/strace: detach: waitpid(12): No child processes
/usr/bin/strace: Process 12 detached

Хто хоче зробити собі те саме, то archwiki має інструкцію для debootstrap, а врапер є осьо нижче:

#!/bin/sh

usage() {
    echo Usage: nspawn-run [-rb] [-d dir_to_bind] container_dir [cmd] 2>&1
    exit 1
}

dirs_to_bind=$HOME
bind=--bind
boot="-u $USER"
while getopts rbd: opt; do
    case $opt in
        r) bind=--bind-ro ;;
        b) boot=-b ;;
        d) dirs_to_bind="$dirs_to_bind $OPTARG" ;;
        *) usage
    esac
done
shift $((OPTIND-1))

[ -d "$1" ] || usage; container=$1; shift
[ 0 = $#  ] || boot="$boot -a"

dirs_to_bind=`printf -- "$bind %s " $dirs_to_bind`
exec sudo systemd-nspawn -q -D "$container" $dirs_to_bind \
     --chdir="$PWD" $boot "$@"

-d /foo -d /bar додає екстра директорії, окрім $HOME; -r монтує їх зсередини контейнера read-only.

henry_flower: A melancholy wolf (Default)

На жаль, це не про двигуни. Тобто про двигуни, але не ті.

Вікіпідійна сторінка аркхайвних форматів ув таблиці Compression only має назви яких я не чув: rzip (шо?), snappy (га?) і деякі інші, про які чув, але користувався ніколи.

Може я missing out?? Протестувавши їх 9 штук можу повторити банальне спостереження про пробіг, що може відрізнятися.

Наприклад, для 15MB éкзек'ютаблу:

$ ./comprtest /usr/libexec/gdb | tee small.txt
szip             0.07  44.73          8617411
gzip             0.60  61.17          6054706
compress         0.16  44.36          8675673
bzip2            0.97  63.54          5684994
rzip             1.15  64.00          5613217
lzip             5.71  70.52          4596226
lzop             0.03  47.27          8220932
xz               5.45  70.58          4586452
zstd             0.06  61.42          6015779

де стовпчик

  • 2--час ув секундах;
  • 3 це 100(1-compressedorig) , тобто чим вищий %, тим є ліпший результата;
  • 4--фінальний розмір.

або барчартом:

$ sort -nk3 small.txt | cpp -P plot.gp | gnuplot -persist

comprtest--пролетарський шелóвський скрипта, який ув лупі годує або всі 9ти аркхайвери, або лише ті, які користувач вказав:

$ ./comprtest /etc/services xz bzip2
xz               0.18  84.65           107724
bzip2            0.03  81.24           131627

Нічого екстра-цікавого він не містить, окрім хфункції cc(), від якої у мене палає:

#!/bin/sh
# shellcheck disable=2016,2086,2068
set -e

archivers='szip gzip compress bzip2 rzip lzip lzop xz zstd'
input=${1:?Usage: comprtest file}; shift
isize=`wc -c < "$input"`

cc() { for c; do command -v $c>/dev/null || { echo no $c 1>&2;return 1;}; done;}
cc time ${@:-$archivers}
tmp=`mktemp -u tmp.XXXXXX`
trap 'rm -f $tmp' 0 1 2 15

for c in ${@:-$archivers}; do
    echo $c
    case $c in
        szip ) args='< "$input" > $tmp' ;;
        rzip ) args='-k -o $tmp "$input"' ;;
        *    ) args='-c "$input" > $tmp'
    esac

    eval "time -p $c $args" 2>&1 | awk '/real/ {print $2}'
    osize=`wc -c < $tmp`

    echo $isize $osize | awk '{print 100*(1-$2/($1==0?$2:$1))}'
    echo $osize
    rm $tmp
done | xargs -n4 printf "%-8s  %11.2f  %6.2f  %15d\n"

Чому ув всіх борн-лайк шелах, окрім башу, команда type друкує помилки на stdout? Замість мінімального

type a b c >/dev/null

який поверне 0, якщо всі з a, b, c є присутні (або відріпортує на stderr якщо хтось не знайшовся і поверне >= 1), треба писати ось цей маразм:

cc() { for c; do command -v $c>/dev/null || { echo no $c 1>&2;return 1;}; done;}

Підозрюю все почалосі з ash, з якого виросли більшість опенсоурсних шелів. Навіть busybox скопіював то майже verbatim, я спеціяльно подивився.

До речі, якщо хтось знає як написати cc() коротше за 80 символів, буду вдячний.

Добро. (Не буде.) Барчарта генерується простим пенсільванісійським ґнуплотом, дейта якому віддає cpp (ґнуплот не бажає зберігати stdin собі до буфера і на графіку з 2ма абсцисами з stdin отримує інфаркта дупи).

$ cat plot.gp
$data <<E
#include "/dev/stdin"
E
set key tmargin
set xtics rotate by -30 left
set y2tics
set ylabel "Seconds"
set y2label "%"
set style data histograms
set style fill solid
plot $data using 2 axis x1y1 title "Time", \
     "" using 3:xticlabels(1) axis x1y2 title "Space saving"

На xml-файлах запхнутих до .tar розміром 314MB, xz програє rzip'у по всім параметрам, а швидкість lzop'у вражає:

$ ./comprtest ~/Downloads/emacs.stackexchange.tar
szip             1.78  63.70        119429565
gzip             7.19  77.59         73724710
compress         4.06  67.17        108015563
bzip2           21.27  83.36         54751478
rzip            17.51  85.93         46304199
lzip           120.57  85.06         49151518
lzop             0.69  63.63        119667058
xz             125.91  85.55         47559464
zstd             1.35  79.40         67766890

Ув якості домашнього завдання, можете додати перевірку до comprtest чи є foo та bar

$ ./comprtest file foo bar

ув змінній archivers.

henry_flower: A melancholy wolf (Default)

Який можна створити лайнаксом аркхайв, такий щоб (а) його нічим було відкоркувати з-під віндюка (без wsl2 або сіґвіну), але (б) який можна прочитати ув fbsd та маку?

Ув старі добрі часи було достатньо cpio, але зараз ув кожного є інстальований 7-зіп, а як ні, то віндюка має bsdtar і такий фокуса більше не працює.

7-зіп читає навіть образи дискóві заформатовані ув ext4, тобто

$ truncate -s 10M file.img
$ mkfs.ext4 file.img
$ sudo mount -o loop file.img /десь
$ sudo cp шось /десь
$ sudo umount /десь

допоможе ніяк. Так само буде з fat, udf та hfsplus.

Але це працює з mkfs.btrfs! 7-зіп каже що воно "Cannot open file 'foo.btrfs' as archive", а ув fbsd мені вдалося замонтувати його через lklfuse (з пекеджу fusefs-lkl).

Мінімальний розмір фс там має бути 114,294,784 байт, що є трохи занадто, але з bzip2, наприклад, фс та пейлоад з 2323 байтів перетворюється разом на 7214 байт.

Осьо він: hello.btrfs.bz2. Всередині є скрипта, який той образ дискóвий згенерував (ув деб'яні йому треба btrfs-progs і libarchive-tools), та побажання українське від щирого серця.

henry_flower: A melancholy wolf (Default)

Коли хтось раптом потребує статичного HTTP сервера для тестів, йому зазвичай рекомендують python -m http.server 8000; колись навіть список таких команд був для багатьох мов.

Закінчується це зазвичай так: людина або цим користується, або починає їхати дахом--писати для себе статичний сервер на awk, на netcat, на whitespace чи brainfuck. Кількість божевільних серед уйоб погромістів загалом вражає і чим простіша задача, тим більше часу вони їй приділяють.

Примітивність an ad hoc http static server in your current directory також є очевидна: цю іграшку викидають як тільки з'являється потреба тестувати сценарії реального життя, наприклад CORS чи клаентську аутентифікацію з сертифікатом. Приклад з пáйфоном не дозволяє навіть перевірити TLS хендшейка.

Чому ув якості http server one-liner не використовувати справжні сервера? Тому що жоден з них (окрім Caddy) такого використання не передбачає, а вагон файлів ув /etc, які очікує хеві-д'юті сервер, його сістемді юніти та мотлох ув /run на спроби не надихають.

Пригадуєте як працює опач з

$ lol() { curl -sL "$1" | nokogiri -e "p \$_.css('$2').size"; }
$ lol https://httpd.apache.org/docs/2.4/mod/directives.html li
733

дайрективами ув

$ lol https://httpd.apache.org/docs/2.4/mod 'dt a'
133

модулях?

Щоб стартувати йому потрібно окрема директорія, в якій він буде шукати конфігурацію, плюватися логами та записувати підфайла.

Сценарій: користувач інсталює опач звичним методом dnf install httpd, &c, а чи використовує він його за призначення на порту 80, нас не цікавить. Той самий системно-інстальйований опач можна змусити читати інший файл конфігурації, слухати на іншому порту і конфліктувати зі системною конфігурацією ніяк.

На жаль, це потребує скриптування, але такий скрипт може служити все життя, тому що нові http сервера з'являються та щезають кожен рік, а опач є вічний.

Я спочатку написав наївний шелівський враппер ув федорі, який робив темпоральну директорію, куди копіював крихітний httpd.conf. Все запрацювало, але коли я спробував той враппер на деб'яні, то на деякий час закрив обличчя руками.

Кожного разу 1 і те саме: мейнтейнер вважає що він знає краще, ніж апстрім. Сабсета модулів які .so, деб'ян компайлить ін (щоб однакові конфіґі на різних ОС працювали ніколи), ім'я дефолтного конфіґу є свое (звичайно), éкзек'ютабл називається опач2 замість httpd (чому ні).

Всі завжди з цим погоджуються, тому шо інакше не дай бог запишуть ув ко-мейнтейнери а це я вибачаюсь! Не для того штани ув фаанґ просиджувати щоб майнтейнерить якийсь пекедж як лох, най 15-річний румун за опачем слідкує, ув перерві між алгеброю та фізикою.

(Вибачте.)

З цікавості подивився на fbsd. Не настільки погано, але всі шляхи до модулей, наприклад, з лайнаксом не співпадають.

Поліпшена версія враперу читає конфігурацію з json.

$ find * -type f | xargs wc -l
  30 conf/httpd.conf
  52 httpd-test
  26 os.json
 108 total

це є забагато щоб постити тут, але ось вам svg (чув що ув журналах, які давно збанкрутували, колись друкували вихідний код програм, який читач мав вдома самотужки ввести):

$ httpd-test ~/Downloads
ID            fedora
log           tail -f /tmp/tmp.7PTHVzUhE5/logs/access_log
conf          /tmp/tmp.7PTHVzUhE5/conf/httpd.conf
TypesConfig   /etc/mime.types
DocumentRoot  /home/alex/Downloads
Listen        127.0.0.1:8000

До речі, всі 133 модуля опачу можна йому скомпайлити статично (або вибірково, лише ті що потрібні: configure --enable-modules=none --enable-mods-static='auth_basic …'). Чому це не використовують усіляки ембедні дівайси замість пародій tiny/micro/nano-something, у яких тече пам'ять і які половину слів http/1.1 не знають, залишається незрозумілим.

tcplol

Jan. 8th, 2024 06:04
henry_flower: A melancholy wolf (Default)

ncat -lk -e cmd 127.0.0.1 8000 з попереднього посту, яке може слухати на 127.0.0.1:8000 і форкати cmd (під'єднуючи діскріптора 0 (stdin) cmd до сокету для для читання та діскріптора 1 (stdout) для писання ув нього), не є чимось оригінальним: саме так працював супер-пупер-сервер inetd або Бернштайновський tcpserver.

Steps performed by inetd

(Кожного разу коли хтось посилається на Бернштайна, я згадую його подорожній нарис як його труїли газом ув готелі СПб літом 2000 року.)

З появою т.з. сокет октивації ув systemd, деякі контори (ойбіем) перестали (x)inetd поставляти взагалі: користувачам залишили або переписування файлів конфігурації inetd ув юніти systemd, або плигання на крижину убунту.

Корисний аспекта від цього був у тому, що супер-пупер-сервер inetd хоча міг зміювати юзера у форку, сам запускався під рута і конфігурацію тримав ув /etc (що вимагало права адміністратора для ігр зі своїми експериментальними сервісами), а systemd запровадив режима --user і міг шукати юніти ув ~/.config/systemd/user/.

Наприклад, складний мережевий сервіс, який питає ім'я клаенту і вітається з ним:

$ cat hello.sh
#!/bin/sh
uname 1>&2
while [ -z "$name" ]; do
    printf "What is your name? "
    read -r name || exit 1
done
echo "Hello, $name!"

можна писати з 0м рядків коду які оперують сокетами, а замість того додати 2 юніт файла:

$ cat ~/.config/systemd/user/hello.socket
[Unit]
Description=hello.sh socket

[Socket]
ListenStream=12345
Accept=yes

[Install]
WantedBy=sockets.target

та

$ cat ~/.config/systemd/user/[email protected]
[Unit]
Description=hello.sh

[Service]
ExecStart=-/home/alex/lib/software/example/ruby/fork/hello.sh
StandardInput=socket

Після чого:

$ systemctl --user daemon-reload
$ systemctl --user start hello.socket
$ ncat 127.0.0.1 12345
Linux
What is your name? Dude
Hello, Dude!

1й рядок з "Linux" з'явився тому що systemd по-замовчуванню під'єднує діскриптора 2 до сокету також, що я вважаю неввічливим.

Замість ncat тут краще перевіряти socat'ом, т.я. ncat не закриває з'єднання після того як systemd закрив сокета і продовжує висіти ув CLOSE_WAIT:

$ netstat -4an | grep 12345
tcp    0  0 127.0.0.1:34552     127.0.0.1:12345     CLOSE_WAIT

(Так само дивно поводиться nc ув busybox; що робить nc з bsd перевіряти лінь.)

Кому подобається писанина з ini-файлами є ув захваті. Всім іншим залишається журитися та писати форкові або тредові сервери вручну.

Емулювати ncat -lk можна також на Рубі, де аналога вдається втиснути ув 37 рядків коду. Той самий діалог

$ socat - TCP4:127.0.0.1:8000
What is your name? Dude
Hello, Dude!

Зі сторони сервера виглядає так:

$ ./tcplol -v -p 8000 ./hello.sh
Client 127.0.0.1:36512
Linux
Client 127.0.0.1:36512, pid 42528: disconnect

stderr під'єднується до сокету тільки з опцією -2.

$ cat tcplol
#!/usr/bin/env ruby

require 'optparse'
require 'socket'

usage = 'Usage: tcplol [-2v] [-h 127.0.0.1] -p 1234 program [args...]'
opt = ARGV.getopts "h:p:2v"
abort usage unless opt['p'] && ARGV[0]
$VERBOSE = nil unless opt['v']

at_exit do
  Thread.list.filter {|t| t != Thread.current}.each do |t|
    warn "\n" + "Waiting for " + t[:cid]
    t.join
  end
end

server = TCPServer.new opt['h'] || '127.0.0.1', opt['p']
loop do
  begin
    client = server.accept
    cid = client.remote_address.ip_unpack.join ':'
  rescue
    warn "Client error: #{$!}"
    next
  end

  warn "Client #{cid}"
  pid = fork do
    $stdin.reopen client
    $stdout.reopen client
    $stderr.reopen client if opt['2']
    client.close
    exec(*ARGV)
  end
  client.close
  cid += ", pid #{pid}"
  Thread.new(cid, pid) do
    Thread.current[:cid] = cid
    Process.wait pid
    warn "Client #{cid}: disconnect"
  end
end

Щоб не залишати зімбі нам доводиться створювати окремого треда для кожного форку. at_exit потрібен лише коли tcplol спілкується з клаентами, але отримує щось на кшталт SIGINT.

Єдина цікава деталь тут є ув чеканні на іксепшона поряд з server.accept. Не знаю чому, але я вважав що лайнаксне ядро деталі TCP handshake ховає та унеможливлює його ламання з сокетного інтерхфейсу (але не з raw sockets, звичайно). Еге. Як прибрати begin…rescue, tcplol гепнеться на спробі дізнатися адресу хоста клаента-злодія, якщо ув tcplol штирнути nmap'ом:

$ nmap -sT -p 8000 127.0.0.1
henry_flower: A melancholy wolf (Default)

Щоб стрімати якесь .mp4 чи .mkv, достатньо будь-якого статичного HTTP сервера, який підтримує range requests. Тоді будь-який бовзер або mpv чи vlc зможе грати кєно з seeking (як це українською?), як буде дозволяти пропускна здатність і майже так гарно, ніби .mkv є локальний файла.

Колись нарід писав спеціяльні сервери для стрімінгу, бороли лейтенсі, оптимізували кійфреймні інтервали, але все це швидко померло як тільки веб бовзери отримали <video> елемент.

Якщо десь на хмарній VM ув країні, якій все одно на те що ви торентите, є колекція святкових фільмів, то дивитися її можна сказавши на сервері

$ ruby -run -ehttpd . -b 127.0.0.1 -p 8000

а дома, на клаенті:

$ ssh -L 127.0.0.1:12345:127.0.0.1:8000 -Nv example.com

щоб форвардити з клаенту 127.0.0.1:12345 на сервер example.com HTTP ріквести і читати відповідь, а потім

$ mpv http://127.0.0.1:12345/movie.mkv

щоб кєно дивитися.

Як зробити щось подібне без HTTP серверу?

mpv вміє читати raw TCP потік. Можна просто чекати підключення на якогось порта та зразу відправляти йому .mkv файла. Але тоді mpv з'їсть увесь bandwidth який є, тому наш сервер ліпше має рейт лімітувати потік байтів.

Спочатку я хотів написати елементарний форк-сервер, але згадав про pv та ncat. З цим дуо достатньо пролетарського шелу:

$ cat mickeymousetube
#!/bin/sh

export movie="${1:?Usage: ${0##*/} file.mkv [port]}"
port=${2:-61001}
type pv ncat || exit 1

__dirname=$(dirname "$(readlink -f "$0")")
ncat -vlk -e "$__dirname/libexec.stream.sh" 127.0.0.1 $port

ncat вміє форкати себе та виконувати інший exe, коли клаент підключається.

$ cat libexec.stream.sh
#!/bin/sh
pv -L2M "$movie"

-L2M тут означає макс 2МБ за секунду.

$ mpv tcp://127.0.0.1:61001

буде файла грати.

Недоліком цього є неможливість seeking на клаенті та якщо щось станеться з сокетом (сусід посвердлить вам інторнет кабеля), то дивитися кєно доведеться з самого початку.

З іншої сторони, якщо робити конкурента тіктоку або ютубних шортс, то seeking там є непотрібно, контін ґєнта таких сервісів його відсутності не помітить.

henry_flower: A melancholy wolf (Default)

Колись на початку 2000х були популярні сервіси віртуальних откриток, де були колекції чиїхось котів с бльосткамі і формою для поздоровлень.

Зараз емулятор терміналу є на будь-якій ОС майже зразу після її інсталяції. Можна штампувати текстові поштівки, які будуть працювати будь-де. Наприклад:

$ fold -w40 hello
tail -c+37 "$0"|base64 -d|gzip -cd
#H4sIAC1EjWUAA62TzQqDMAyA7z71/tA5GLLbdjK
pwthtJ90Y2HfJk8ymrbTaThlCkDQ/X9I0UiVpDUl
mIlASbJSIz2JQnyNGlJIRQHgIsPx4B6Qrjyna0rL
36IH8+DgIMj521sJH8foDxJeKt7AMhJP6ypiuBcq
WgaqaJQ4CH+TGh/bobktFxHi7UWJoIYNbY5oqmPK
cuuY2+4cgf4ULwoLEmwvmtnJOwBY8KwVKEg+q+nZ
uvNC5SoELYUNw0rmJGa1amdrMEvYEV3M79a/tWNc
FUhJbNSDzGji8ib5aa4fXsN5YY8PSWX1wSUfXATL
5AjV3WkpHBAAA

допитливому тхіру скаже мало, доки він скрипта не запустить:

$ ./hello
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⡿⢿⣿⣿⣿⣿⡟⠀⢠⡄⠀⢻⣿⣿⣿⣿⡿⢿⣿⣿⣿
⣿⣿⣿⠀⠀⣿⣿⣿⡟⠀⢀⣿⣷⠀⠈⣿⣿⣿⣿⠀⠀⣿⣿⣿
⣿⣿⣿⠀⠀⣿⣿⣿⠇⠀⣾⣿⣿⣇⠀⢸⣿⣿⣿⠀⠀⣿⣿⣿
⣿⣿⣿⠀⠀⣿⣿⣿⠀⢠⣿⣿⣿⣿⠀⠀⣿⣿⣿⠀⠀⣿⣿⣿
⣿⣿⣿⠀⠀⣿⣿⡇⠀⢸⣿⣿⣿⣿⡆⠀⣿⣿⣿⠀⠀⣿⣿⣿
⣿⣿⣿⠀⠀⣿⣿⡇⠀⢸⣿⣿⣿⣿⡇⠀⣿⣿⣿⠀⠀⣿⣿⣿
⣿⣿⣿⣤⣤⣿⣿⡇⠀⢸⣿⣿⣿⣿⠇⠀⣿⣿⣿⣤⣤⣿⣿⣿
⣿⣿⣿⣿⣿⣧⠀⠀⣀⣀⣀⣀⣀⣀⣀⣀⠀⠀⣾⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⡄⠀⢻⣿⣿⣿⣿⣿⣿⠏⠀⣰⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⢿⣿⣿
⣿⡏⢹⣿⠉⣿⣿⠉⠹⣿⡍⠹⠟⢩⣏⠙⡿⢉⡏⠑⡶⠊⢹⣿
⣿⡇⢠⣤⠀⣿⠃⠘⠀⢻⡿⠂⠀⢿⣿⠆⢁⣾⡇⠀⢡⠀⢸⣿
⣿⣷⣾⣿⣶⣷⣶⣿⣷⣶⣶⣾⣷⣶⣷⣶⣿⣿⣷⣶⣿⣶⣾⣿

(Цей ascii art я знайшов на гітхабі.)

Я би залишив результат gzip'у ув коментарі і так, без base64, але тоді у bash стається інфаркт, коли він бачить '\0'. Shebang, якщо хтось забув, у багатьох випадках є необов'язковим.

hello був згенерований ось цим скриптом, який читає повідомлення для користувача з stdin:

#!/bin/sh

prefix() { printf 'tail -c+37 "$0"|base64 -d|gzip -cd\n#'; }

script=`mktemp`
trap 'rm -f $script' 0

prefix > "$script"
gzip -c | base64 -w0 >> "$script"
chmod +x "$script"
mv "$script" "${1:-hello}"

Можна піти ще далі та генерувати секретну поштівку яка вимагає пароля для показу поздоровлення. На жаль, у такому випадку, гарантія на присутність потрібної утілити (openssl) є відсутня. Є варіянт з xor cipher'ом на awk, чи з тасканням вихідного коду якогось chacha20 на C (але тоді отримувач має мати компілятора), або з вбудованим ув скрипта αcτµαlly pδrταblε εxεcµταblε, який буде байти розшифровувати.

Про тулчейна Cosmopolitan Libc чули майже всі під час кетайської чуми, але я не бачив щоб хтось його використовував.

Наприклад, мінімальна реалізація rc4:

#define S ,t=s[i],s[i]=s[j],s[j]=t /* rc4 hexkey <file */
unsigned char k[256],s[256],i,j,t;main(c,v,e)char**v;{++v;while(++i)s[
i]=i;for(c=0;*(*v)++;k[c++]=e)sscanf((*v)++-1,"%2x",&e);while(j+=s[i]
+k[i%c]S,++i);for(j=0;c=~getchar();putchar(~c^s[t+=s[i]]))j+=s[++i]S;}

компілюється ув такий exe

$ file rc4
rc4: DOS/MBR boot sector; partition 1 : ID=0x7f, active,
start-CHS (0x0,0,1), end-CHS (0x3ff,255,63), startsector 0, 4294967295 sectors
$ du rc4
404K    rc4

який запускається на лайнаксі (x86_64 чи aarch64) або fbsd (має також на маку, але я не перевіряв).

Тоді секретна поштівка буде виглядати так:

  1. байнарі rc4 пхається ув коментар;
  2. ув наступному коментарі--зашифроване поздоровлення;
  3. скрипта слайсить потрібні діапазони самого себе ($0) щоб видобути пп. 1-2;
  4. питає пароля, розшифрофує текта.

Щоб згенерувати це, простіше всього використати якийсь templating engine, наприклад рубівський erb:

$ cat message.erb
#<%= rc4=File.read(rc4) %>
#<%= message=File.read(message) %>
slice() { tail -c+$1 "$0" | head -c $2 | base64 -d | gzip -cd; }
cleanup() { rm -f "$rc4"; stty echo; }
set -e
rc4=`mktemp`
trap cleanup 0
trap 'cleanup; echo 1>&2; exit 1' 1 2 15
slice 2 '<%= rc4.length %>' > "$rc4"
chmod +x "$rc4"
stty -echo
printf 'Password: ' 1>&2; read -r password; printf "\n" 1>&2
[ -n "$password" ]
hexkey=`printf '%s' "$password" | od -A n -t x1 -v | tr -d ' \n'`
slice '<%= rc4.length+4 %>' '<%= message.length %>' | "$rc4" $hexkey

erb пускається з параметрами rc4= та message=

$ erb rc4=rc4.text message=message.text message.erb

де *.text є файли з підготовленим base64.

Мейкфайла:

out := _out
password := monkey
message := hello1.txt
CC := ~/opt/s/cosmocc/bin/cosmocc

all: $(out)/message

$(out)/%: %.c
    @mkdir -p $(dir $@)
    $(CC) --std=c89 -o $@ $<

$(out)/message: message.erb $(out)/rc4
    gzip -c < $(out)/rc4 | base64 -w0 > rc4.text
    $(out)/rc4 $(hexkey) < $(message) | gzip -c | base64 -w0 >message.text
    erb rc4=rc4.text message=message.text $< > $@
    chmod +x $@
    rm *.text

.DELETE_ON_ERROR:
hexkey = $(shell printf '%s' $(call se,$(password)) | od -A n -t x1 -v | tr -d ' \n')
se = '$(subst ','\'',$1)'

Космополітанський тулчейна очікується ув ~/opt/s/cosmocc/.

$ make password="всім нам" message=hello2.txt
...
$ du _out/message
304K    _out/message

Так, 304KB здається трохи забагато, але по сьогоднішнім стандартам це є ніщо.

$ _out/message
Password:
               ▁
              ▃ ▅▖
             ▘    ▄
    ┏▃      ▌  ▘▌  ▖     ▃▄▌
    ▎ ▌    ▌  ▌  ▖ ▊    ▊  ▎
    ▎ ▌    ▎ ▗   ▌  ▌   ▊  ▎
    ▎ ▌   ▌  ▌    ▎ ▊   ▊  ▎
    ▎ ▌   ▌ ▊     ▊  ▎  ▊  ▎
    ▎ ▌   ▎ ▌     ▌  ▎  ▊  ▎
    ▎ ▌  ▊  ▌     ▊  ▌  ▊  ▎
    ▎ ▌  ▊  ▌     ▊  ▌  ▊  ▎
    ▅▅▘▂▂▂▎ ▊▂▂▂▂▂▂  ▂▂▂▖▅▅▘
       ▌  ▁▁▁▁▁▁▁▁▁▁▁   ▎
       ▝▖ ▝         ▌  ▗
        ▝▄▄▃       ╺▄▄▄
                         ▗▂▂▄
 ▊▅▌ ┏▖  ▃▅▖ ▄▅▄▃▅╸▄▄▁▃▄▌▅▌▗▅▖
 ▊ ▆▆ ▊ ▗ ▄ ▖ ▊  ▌ ▝▖▅▗▘▊ ▄▗ ▌
 ▊▂▌ ▂┛▌▂▅▅▂▂▄▂▅▃▂▖▄▂▄  ▌▂▄▊▂▌

Бовзери це рендерять погано.

henry_flower: A melancholy wolf (Default)

Як ви пишете 25м3? Ув маркдауні це виглядає рівно так, як можна очікувати:

25м<sup>3</sup>

Деякі редактори (імакс) дозволяють друкувати superscript цифри; наприклад для надрядкового 2 треба віртуозно натиснути C-x 8 ^ 2. Надрядкову a так надрукувати не вийде--замість неї з'явиться літера â.

Юнікод мав би мати надрядкові та підрядкові симболи для кожної абетки, але цього не сталося. Присутній сабсет--для, наприклад, латини та безнадійної кирилиці--розмазан по випадкових місцях. Якщо підрядкові цифри можна отримати знаючи офсета, то для абеток це працювати не буде: e.g., підрядкова літера j сидить ув секції Latin Extended-C, втиснута поміж загадковою фінно-угорською (яка дуже нагадує мотематичний квантор існування) та надрядковою великою V.

Юнікод ніби має спеціяльну секцію Superscript and Subscripts, але вона виглядає як поганий жарт:

Superscript and Subscripts

(Пусті світлі клітинки це не помилка рендерінґу симбола, а "not assigned" місця.)

Врешті-решт, якщо ретельно пошукати, знайти локацію більшості (не всіх) над/підрядкових літер можна, і тоді написавши елементарний скрипт що робить заміну звичайних літер на над/підрядкові, будь-який пристойний редактор дозволить замінити виділений текста на результат роботи скрипта.

Це є звичайно, цікаво, але було би цікавіше виділяти маркапний варіянт де трансформувалися би лише елементи <sup> чи <sub>, наприклад для хформули y = x2a:

Працює це так. 1й скрипт робить заміну відповідних симболів:

$ lsb_release -d | ./supsub sub
dₑₛ𞁞ᵣᵢₚₜᵢₒₙ:    fₑdₒᵣₐ ᵣₑₗₑₐₛₑ ₃₉ ₍ₜₕᵢᵣₜᵧ ₙᵢₙₑ₎
$ lsb_release -d | ./supsub sub | ./supsub invert
ᵈ𞀵ˢ𞀿ʳⁱ𞀾ᵗⁱ𞀼ⁿ:    ᶠ𞀵ᵈ𞀼ʳ𞀰 ʳ𞀵ˡ𞀵𞀰ˢ𞀵 ³⁹ ⁽ᵗʰⁱʳᵗʸ ⁿⁱⁿ𞀵⁾
$ lsb_release -d | ./supsub sub | ./supsub invert | ./supsub restore
dеsсrірtіоn:    fеdоrа rеlеаsе 39 (thіrty nіnе)

(Рендерінґ бовзерами бажає залишати кращого.)

$ cat supsub
#!/usr/bin/env -S ruby --disable-gems

db = DATA.read.split(/\s+/).filter {|v| v}
$supa = db.map {|v| [v[0], v[1]] }.to_h
$sub = db.map {|v| [v[0], v[2]] }.to_h

mode = 'sup|sub|invert|restore'
abort "Usage: supsub #{mode} < file.txt" unless ARGV[0] =~ /^#{mode}$/

def tr mode, chars
  case mode
  when 'sup'
    chars.map { |ch| $supa[ch.downcase] || ch }.join
  when 'sub'
    chars.map { |ch| $sub[ch.downcase] || ch }.join
  when 'invert'
    supa_v = $supa.invert
    sub_v = $sub.invert
    chars.map do |ch|
      if supa_v[ch]
        $sub[supa_v[ch]] || ch
      elsif sub_v[ch]
        $supa[sub_v[ch]] || ch
      else
        ch
      end
    end.join
  else # restore
    supa_v = $supa.invert
    sub_v = $sub.invert
    chars.map { |ch| supa_v[ch] || sub_v[ch] || ch}.join
  end
end

while (line = STDIN.gets)
  print tr(ARGV[0], line.chars)
end

__END__
0⁰₀ 1¹₁ 2²₂ 3³₃ 4⁴₄ 5⁵₅ 6⁶₆ 7⁷₇ 8⁸₈ 9⁹₉ +⁺₊ -⁻₋ =⁼₌ (⁽₍ )⁾₎
aᵃₐ bᵇb cᶜ𞁞 dᵈd eᵉₑ fᶠf gᵍg hʰₕ iⁱᵢ jʲⱼ kᵏₖ lˡₗ mᵐₘ nⁿₙ oᵒₒ
pᵖₚ q𐞥q rʳᵣ sˢₛ tᵗₜ uᵘᵤ vᵛᵥ wʷw xˣₓ yʸᵧ zᶻz
а𞀰ₐ б𞀱𞁒 в𞀲𞁓 г𞀳𞁔 ґґ𞁧 д𞀴𞁕 е𞀵ₑ ж𞀶𞁗 з𞀷𞁘 и𞀸𞁙 іⁱ𞁨 їїї ййй
к𞀹𞁚 л𞀺𞁛 м𞀻ₘ нᵸн о𞀼ₒ п𞀽𞁝 р𞀾ₚ с𞀿𞁞 т𞁀т у𞁁𞁟 ф𞁂𞁠 х𞁃𞁡 ц𞁄𞁢
ч𞁅𞁣 ш𞁆𞁤 щщщ ьꚝь ю𞁉ю яяя

Замінювати воно може лише цифри, симболи латини та української абетки. Сили на пошук голок ув юнікоді у мене закінчилися.

2й скрипта є трохи цікавіший:

$ echo '<sub>test</sub> <i>lol</i> <sup>haha</sup> but <sup>it works</sup>!' | ./supsub-xml
ₜₑₛₜ <i>lol</i> ʰᵃʰᵃ but ⁱᵗ ʷᵒʳᵏˢ!

За допомогою 鋸 парситься фрагменти xml та викликається попередній скрипта (supsub) коли зустрічається елемент <sup> чи <sub>. Версія 1 скрипта supsub-xml може бути такою примітивною як

#!/usr/bin/env ruby

require 'nokogiri'

cmd = ARGV[0] || File.join(__dir__, 'supsub')
doc = Nokogiri::HTML.fragment STDIN.read

doc.css('sup,sub').each do |node|
  IO.popen("#{cmd} #{node.name}", 'w+') do |t|
    t.write node.text
    t.close_write
    node.replace t.gets
  end
end

print doc.to_s

Воно працює, і, для більшості випадків його можна залишити як є, але форкання на кожну трансформацію виглядає варварським.

Стівенс в своїй APUE розповідає про coprocesses, які він описує як процесси які живуть одночасно з головною програмою (parent), і до яких parent під'єднується через 2 пайпи, які він сам створює:

Driving a coprocess

Bash підтримує коупроцеси, але використовують їх ~ніхто, тому що ⓐ нарід ув більшості не гребе шо то за коупроцеси такі, ⓑ пише майкросервіси на расті для калькуляції 6*8, ⓒ це є забута стародавня технологія предків.

Найпростіший приклад виглядає так: parent запускає звичайну утіліту tr(1), яка має робити upcase для всього, шо надходить на її stdin:

$ cat coprocess
#!/usr/bin/env ruby

parent_in, parent_out = IO.pipe
child_in, child_out = IO.pipe
spawn "tr '[a-z]' '[A-Z]'", in: child_in, out: parent_out
parent_out.close
child_in.close

child_out.puts "lol"
print parent_in.gets
child_out.puts "haha"
print parent_in.gets

На жаль, як запустити ./coprocess, ми не отримаємо LOL та HAHA: скрипта зависне ув дедлоку на рядку print parent_in.gets: tr з'їсть свій stdin і напише результат ув свій stdout, використовуючи fwrite(2) з libc (принаймі лайнаксна версія з coreutils), яка буферує результат, тому parent буде чекати на байти з пайпу вічно.

Зарадити цьому можна пустивши tr через утіліту stdbuf(1):

spawn "stdbuf -i0 -o0 tr '[a-z]' '[A-Z]'", in: child_in, out: parent_out

тоді дейта почне рухатися пайпами:

$ ./coprocess
LOL
HAHA

Цей хфінт вухами працює лише для хфункцій з libc. Рубі (як і інші мови) має свій IO механізм, тому stdbuf йому допоможе як дикобразу вогнемета.

Але вихід є: якщо змусити коупроцесс думати що той є підключений до терміналу, тоді спрощена схема роботи supsub-xml що форкає supsub буде виглядати на кшталт (NSFW):

coprocess via pty

Рубі має чудове вбудоване ікстеншона pty, яке дозволяє замість IO.pipe для parent писати PTY.open, не чіпаючи все інше.

$ cat supsub-xml
#!/usr/bin/env ruby

require 'nokogiri'
require 'pty'

cmd = ARGV[0] || File.join(__dir__, 'supsub')

class Coprocess
  def initialize cmd
    @master, slave = PTY.open
    read, @write = IO.pipe
    spawn cmd, in: read, out: slave
    read.close
    slave.close
  end

  def puts str; @write.puts str; end
  def gets; @master.gets; end
end

transforms = {
  "sup" => Coprocess.new("#{cmd} sup"),
  "sub" => Coprocess.new("#{cmd} sub")
}

doc = Nokogiri::HTML.fragment STDIN.read
doc.css('sup,sub').each do |node|
  tr = transforms[node.name]
  tr.puts node.text
  node.replace tr.gets.chomp
rescue
  warn "transforming `#{node.text}` failed: #{$!}"
end

print doc.to_s

Ув версії 2 ми створюємо 2 коупроцесси для елементів <sup> та <sub> заздалегідь і використовуємо їх як локальні майкросервіси. Якщо додати sleep ув кінець скрипта, можна подивитися як це виглядає фізично:

$ $$
bash: 42396: command not found
$ echo '<sub>test</sub> <i>lol</i> <sup>haha</sup>!' | ./supsub-xml
ₜₑₛₜ <i>lol</i> ʰᵃʰᵃ!

поки воно спить, з іншого терміналу:

$ pstree -p 42396 -al
bash,42396
  └─ruby,100032 ./supsub-xml
      ├─ruby,100033 --disable-gems ... sup
      └─ruby,100034 --disable-gems ... sub

$ file /proc/100032/fd/* # supsub-xml
...
$ file /proc/100033/fd/* # supsub sup
...
henry_flower: A melancholy wolf (Default)

Не пам'ятаю як, але натрапив вчора на Торвальдське ріпо з його текстовим редактором, яким, окрім нього, користується ніхто. Цікавого ув ріпо було небагато, якщо не рахувати файла з назвою UTF-8-demo.txt.

Кроум рендерить його майже коректно за вийнятком блоку з формулами. Імакс робить так само, але додатково інкоректно вишиковує судоґрафіку.

Близький до ідеального рендерінґ роблять xterm, gvim та gedit.

(Це є векторний скріншота, який можна зробити через завдяки за допомогою спеціяльного плагіну libgtk-vector-screenshot.so; на жаль, лише для gtk3.)

Якщо придивитися до символів, видно що все перелічені аплікації шукають ґліфи, яких немає ув поточному шрифті, деінде. На федорі, кроумівські дівелопер тулз доповідають список використаних шрифтів так:

Liberation Mono     — Local file (5,438 glyphs)
DejaVu Sans         — Local file (1,179 glyphs)
Droid Sans Thai     — Local file (415 glyphs)
Droid Sans Ethiopic — Local file (320 glyphs)
Segoe UI Historic   — Local file (45 glyphs)
Noto Sans Math      — Local file (7 glyphs)
Droid Sans Fallback — Local file (5 glyphs)
Segoe UI Symbol     — Local file (1 glyph)
Noto Color Emoji    — Local file (1 glyph)
Times New Roman     — Local file (1 glyph)

де Liberation Mono--мій основний діфолтний моноспейсний шрифт.

Я захотів перевірити чи є у мене інстальваний шрифта, який має, якщо не весь, то хоча би значний сабсет UTF-8-demo.txt. Як змусити cairo+pango+harfbuzz+fontconfig не робити font fallback я не знаю (життя є занадто коротким), але можна взяти pandoc, який по діфолту таких фокусів не знає.

Ось ця шелівська хфункція конфертує текста ув pdf:

# input output font
txt2pdf() {
    awk '{print "    " $0}' < "${1:-/dev/null}" | pandoc --pdf-engine=xelatex \
     -V "monofont:${3:-Roboto Mono}" -V "mainfont:${3:-Roboto Mono}" \
     -V geometry:"top=1cm,left=1cm,bottom=1.5cm,right=1cm" \
     -t pdf -o "${2:-${1%.*}.pdf}"
}

(Так, воно змушує pandoc думати шо інпута є маркдаун.)

Тоді після

$ txt2pdf UTF-8-demo.txt lol.pdf 'Ubuntu Mono'

видно, що контемпоральний стиль шрифта Убунту, який не лише contains characteristics unique to the Ubuntu brand, але також conveys a precise, reliable and free attitude, натрапляє на деякі труднощі, щоб не сказати вульгарніше.

ОК, отримати увесь список моноспейсних шрифтів можна за допомогою fontconfig:

$ type fc.mono
fc.mono is aliased to `fc-list :mono family | awk -F, "{print \$1}" | sort -u'
$ fc.mono
Bitstream Vera Sans Mono
Courier 10 Pitch
Courier New
Cursor
DejaVu Sans Mono
Droid Sans Mono
Inconsolata
Liberation Mono
Ligconsolata
Material Icons
Material Icons Outlined
Material Icons Round
Material Icons Sharp
Material Icons Two Tone
Nimbus Mono PS
Noto Color Emoji
Source Code Pro
Terminus
Ubuntu Mono

(Тут має бути також Roboto Mono v2, але.)

Тоді

$ (IFS=$'\n'; for fn in `fc.mono`; do txt2pdf UTF-8-demo.txt "$fn".pdf "$fn"; done)
$ ls *pdf -1
'Bitstream Vera Sans Mono.pdf'
'Courier New.pdf'
'DejaVu Sans Mono.pdf'
'Droid Sans Mono.pdf'
Inconsolata.pdf
'Liberation Mono.pdf'
Ligconsolata.pdf
'Nimbus Mono PS.pdf'
'Source Code Pro.pdf'
'Ubuntu Mono.pdf'

з яких 'DejaVu Sans Mono.pdf' має найкращий результат. Цього всього можна було не робити, а замість того спитати чатджипіті, який на питання 'Which monospace font can render all glyphs from the famous "UTF-8-demo.txt" file?' відповів 'One such font is DejaVu Sans Mono', падлюка.

Якби це був сучасний блоґ, тобто канадійський ютубний ченел як у всіх нормальний людей, я би додав "напешіть ув коментс ЯКИЙ ВАШ ШРИФТА є найюлюбленіший, ЯК У ВАС рендериться ПІДІЕФ, яку думку маєте!"

henry_flower: A melancholy wolf (Default)

Пачіняючі прімус, тобто шукаючи причину чому прибацаний файловий диспетчер KDE Dolphin не хотів копіювати файли, коли виконувався не з KDE, мені спало на думку, як би виглядав мінімальний аналог cp(1) на C, вихідний код якого <= 280 байт.

Ла спеціфікасьйон:

  • mycp src dest;
  • src та dest то є лише файли;
  • компілюватися "clang -Wall mycp.c -o mycp" має з 0 попереджень з лайнакса чи мак'ос;
  • показувати помилку коли (та вертати exit code >= 1):
    • src/dest є неприпустимі (файл не існує, є директорією, немає дозволу на читання/запис, тощо);
    • src == dest (ув мінімальному вигляді, не треба різолвіти шлях/сімлінка);
    • шось погане трапилося під час копіювання.
  • не створювати dest розміром 0 байт, якщо src є дефективне.

Версія clang у мене

$ clang --version | head -1
clang version 16.0.5 (Fedora 16.0.5-1.fc38)

Хфінальний результата виглядає ось так

$ cminify mycp.c | wc -c
280

$ cminify mycp.c | clang-format
#include <err.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(int c, char **v) {
  c - 3 || !strcmp(v[1], v[2]) ? errx(1, "?") : 0;
  int b[65536], s = open(v[1], O_RDWR), d;
  s < 0 ? err(2, "") : 0;
  d = open(v[2], O_RDWR | O_CREAT | O_TRUNC, 420);
  while ((c = read(s, b, 65536)))
    c - write(d, b, c) ? err(3, "") : 0;
}

cminify то є якийсь пáйфонівський скрипта з ґітхабу, якщо хтось знає ліпший варіянт, скажіть. Чому для clang-format немає якогось uglify стилю ув комплекті, то це є цікаве питання також.

Енівей, code golf технікс, які були використані:

  • страшенна економія ув 1 байт:

    • октал 0644 → десяткове 420
    • a==ba-b
    • if (a)b();a?b():0; (шкода що не можна опустити нуль після :)
  • кома;

  • хвункції з err.h, які не є позіксом per se, але є всюди, окрім віндюка.

Дуже дратує, що не виходить позбутися #include ... без вказання версії C якось печерного 1989 року. Замість O_RDWR і ко можна було б вказати число, але портáбільність тоді миттєво гасне.

Тести:

$ ./mycp
mycp: ?
$ ./mycp 1 1
mycp: ?
$ ./mycp /root/nope 1
mycp: : Permission denied
$ ./mycp mycp.c .
mycp: : Bad file descriptor
$ ./mycp . 1
mycp: : Is a directory
$ ./mycp BUGUS 1
mycp: : No such file or directory

1 GB мотлоху має скопіюватися без помилок:

$ head -c $((1024*1024*1024)) /dev/urandom > 1
$ ./mycp 1 2
$ cmp 1 2
$ echo $?
0

Мініфікаційна версія як гарний svg:

$ pango-view -q --font='Ubuntu Mono 24' --rotate 10 --margin=0 <(cminify mycp.c | fold -w18) -o mycp.svg

henry_flower: A melancholy wolf (Default)

Автор маловідомої мови Python, перед тим як переїхати до Гамерики, був членом пацифістської партії Голландії.

Про цікаві зв'язки цієї групи людей з комуністами ув вікіпідіа все є ретельно вимито пральним порошком, але можна знайти деталі в інших статтях:

  • The party was formed from Marxists in the left wing of the Labour Party.
  • Ще раніше існувала така собі Socialistische Werkers Partij, яка "proclaimed its solidarity with the Soviet Union", але КДБ не звертала на них увагу, тому вони хутко приєдналися до партії, в якій потім з'явився Гвідо ван Россум.

Про пейзаж 80х та свої погляди на життя Гвідо розповідає так:

"... there was a big movement in the Netherlands to kick out American weapons that were posted in the Netherlands and throughout all Western European countries as long as they were part of NATO. Because that—it wasn't always completely crystal clear that the Russians were the bad guys. It sometimes felt more like, "Well, there are two sides, and they're clearly disagreeing with each other, but it's not clear who's right." And so I never felt comfortable with the communist system, but I did feel that a socialist system was better than capitalism. And that, yeah, that probably colored some of my views on life in general."

Розумієте, тоді не завжди було зрозуміло хто є поганий, русскіє чи Нейто. Обидві сторони мали слушні думки, але Гвідо завжди здавалося що капіталізме є гірший за сосіалізме.

З Нейто загалом та гамериканською войенщиною конкретно, Гвідо боровся так:

  • ув 90х хоробро приїхав до Вірджинії та вибив ґранта на свою мову пограмування у DARPA. Щоб останні нічого не запідозрили, ґрант був про лілове "комп'ютерне пограмування для всіх".

Для прославлення соціялізму та доказу про неналежність капіталізму до сучасного світу, він:

  • приєднався до 2х каліфорнійських стартапів;
  • працював ув Гооглі та Дропбоксі;
  • наразі допомає знищити останні паростки буржуазії ув Майкрософті.

"Politically, my parents leaned left, my mother a bit more than my dad", згадує Гвідо. Його батько настільки зневажав кровожерливу Королівську Армію Нідерландів, що добровільно сів ув в'язницу, в якості альтернативи призиву.

henry_flower: A melancholy wolf (Default)

З бритишського ріпорту від Royal United Services Institute for Defence and Security Studies:

План монголів для Києва, яких їм вдалося здійснити на півдні України:

The Russian counterintelligence regime on the occupied territories had compiled lists that divided Ukrainians into four categories:

  • Those to be physically liquidated.
  • Those in need of suppression and intimidation.
  • Those considered neutral who could be induced to collaborate.
  • Those prepared to collaborate.

For those in the top category, the FSB had conducted wargames with detachments of the Russian Airborne Forces (VDV) to conduct kill-or-capture missions. In many cases, the purpose of capture was to put individuals involved in the 2014 Revolution of Dignity (often referred to as the Maidan Revolution) on trial to be executed. Although initial lists of persons in the second category existed, the approach was to be more methodical, with the registration of the population through door-to-door sweeps and the use of filtration camps to establish counterintelligence files on large portions of the population in the occupied territories. Filtration would be used to intimidate people, to determine whether they needed to be displaced into Russia, and to lay the groundwork for records to monitor and disrupt resistance networks. Over time, Russia would bring teachers and other officials from Russia itself to engage in the re-education of Ukrainians.

src

Згідно русскіх, всі, хто хотів посилення економічних з'вязків з ЄС у 2013, заслуговують смерті.

henry_flower: A melancholy wolf (Default)

Знайшов 2 коротких оповідання початку 20го століття як імігрували до канадщини.

Пішла поговірка по селах, що приїхав чоловік Бог знає звідки і хоче провадити людей до якоїсь Америки і хоче провадити людей Бог зна куди.

...

Судия каже: «Ти нарід провадиш, щоб їхав».

Я кажу: «Ні, вони сами хотять їхати».

Судия до мене: «Ти чому язик не тримав за зубами? Треба їхати самому, а не стягати других людей за собою. Ти продав нарід аґентови. Наш найяснійший цісар поміг людям вернути з Арґентини, трийцять родин на свій власний кошт, а ти хочеш, щоби цісар знову помагав, як людям щось злого станеться?»

Epub

henry_flower: A melancholy wolf (Default)

Зробив epub з мемуарів Gary Kildall'а. Це є дядько, який написав CP/M, віріянт якої купив Ґейтс у іншої контори і перейменував на DOS. GK помер у 1994, а вікіпідіа має конспіративну версію, що його було вбито.

Текст є дуже цікавий і місцями дотепний. Епізод, де його батько відмазав його від В'єтнаму, дуже смішний.

Про БҐ:

"He had no problem competing with his customers. And, this is where we differed. Monetarily, Bill had the proper approach to get a stack of bucks, and lose a bunch of friends in the meantime."

...

"I was always apprehensive of his business moves, as I found his manner too abrasive and deterministic, although he mostly carried a smile through a discussion of any sort.

"Gates is more an opportunist than a technical type and severely opinionated even when the opinion he holds is absurd."

Мемуари короткі (на кілька годин макс, дії закінчуються у 1980), тому що його родичи дали CHM музею ув 2016 лише перші 7 глав у кострубатому pdf.

Ліцензія на оригінальний pdf є така, що якщо мене спіймають, то червертують після Аутодафе, а рештки кинуть у багаття, тому ріпо та файли epub/mobi на анонімному акаунті:

Download Here

Якщо помітите хибодрук, напишіть ув коментарях, або відкрийте гітлабівську issue.

Page Summary

May 2025

M T W T F S S
   12 34
5678910 11
12131415161718
19202122232425
262728293031 

Expand Cut Tags

No cut tags
OSZAR »