( lacos | 2024. 05. 05., v – 17:53 )

A filter gyerekprocesszkénti elindításával szándékosan nem foglalkoztam.

Mivel itt nem többszálú a parent (= az ipf), azért lehetne pipe() + fork() + dup2() + system()-et használni. [*] De még ebben az esetben is sok munka lenne a hibakezelést és a szignálok kezelését gondosan, szabályosan megcsinálni. (Az ipf az első processz, a fork() után van ugye egy child processz, akkor abban a system() elindít egy shell-t mint grandchild processzt, a shell meg a felhasználó által megadott pipeline minden egyes parancsához forkol még egy-egy "great-grandchild" processzt... És akkor még ott van az a még korábbi probléma, hogy egy pipeline-ban az utolsó pozíció előtt szereplő parancsok exit status-ával nem szoktunk törődni.)

Taknyolni (= hibakezelést kihagyni vagy elnagyolni) én nem vagyok hajlandó, a rendes kidolgozására pedig nincs időm (nem tartozik a core feature-höz amúgy sem!), úgyhogy ezért használ a program szándékosan named pipe-okat (FIFO-kat). Ez volt nagyjából a legelső döntés, amit meghoztam :) Eredetileg még getopt()-ot sem használtam (rögzített számú és sorrendű operandusokat várt a program), de a getopt-ot nem volt nehéz vagy terjedelmes belerakni, és így legalább az option-argument-ek sorrendje variálható, nem kötött. A szignálkezelés elkerülhetetlen volt, mert megszakításnál a queue directory-t ki kell pucolni. Ez a minimum, tehát ennyi elég is. :)

(

[*] Többszálú programból fork() hatására csak egy egyszálú gyerekprocessz ágazik le; az a szál létezik benne, amelyik a fork()-ból visszatér, a többi eredeti szál nem. Ennek következtében a gyerekben a fork() után (és az exec előtt) kizárólag async-signal-safe függvényeket lehet hívni, mert bármi más függvény olyan belső (libc) lock-októl függhet, amelyeket a gyerekprocesszben már senki sem fog feloldani (-> deadlock) -- az a szál, akinél a lock van, a gyerekprocesszben egyszerűen nem létezik. Emiatt nem lehet például fork() után malloc()-ot, execlp()-t / execvp()-t (!), system()-et stb. használni. Rögtön adódik a probléma, hogy a shell-jellegű parancssor parse-olást, amit normálisan a system()-re bíznánk, nem lehet a system()-re bízni. Adódik még az a probléma is, hogy a PATH search nem bízható az execlp()-re. Tavaly nyáron írtam egy async-signal-safe execvp() helyettesítőt; elképesztő mennyiségű munka volt, különösen a unit tesztek:

  1. https://gitlab.com/nbdkit/libnbd/-/commit/aa696c0a6a9d39e72b182c4bf5449…
  2. https://gitlab.com/nbdkit/libnbd/-/commit/0b7172b3cffaae23fa182a2d160f8…
  3. https://gitlab.com/nbdkit/libnbd/-/commit/0a083bc835a26476d563c91b970c2…
  4. https://gitlab.com/nbdkit/libnbd/-/commit/f4b8a2fac73068b03f697c84daaa7…

)

... Az ipf-et valószínűleg nem ajánlanám nem-interaktív használatra (szkriptelni lehet, de akkor a szkriptet érdemes szemmel követni); eleve olyan a felhasználási területe, hogy csak végső esetben érdemes hozzányúlni (ahogy a README.md-ben is írtam, én is azt ajánlom, hogy inkább vegyen a felhasználó több diszket). Szkriptből meg valahogyan így futtatnám (elnagyolva):

# recompress from gz to xz

unset filter1_pid
unset filter2_pid

cleanup()
{
  kill $filter1_pid $filter2_pid
  rm orig-fifo.$$ middle-fifo.$$ filtered-fifo.$$
  rmdir queue-dir.$$
}

trap cleanup EXIT

mkdir queue-dir.$$
mkfifo orig-fifo.$$ middle-fifo.$$ filtered-fifo.$$

gzip -d <orig-fifo.$$ >middle-fifo.$$ &
filter1_pid=$!

xz <middle-fifo.$$ >filtered-fifo.$$ &
filter2_pid=$!

ipf -f file -w orig-fifo -r filtered-fifo -d queue-dir -s ...
ipf_status=$?

wait $filter1_pid
filter1_status=$?
unset filter1_pid

wait $filter2_pid
filter2_status=$?
unset filter2_pid

if [ 0 -eq $ipf_status ] && 
   [ 0 -eq $filter1_status ] &&
   [ 0 -eq $filter2_status ]; then
  ...
fi