123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367 |
- ;;; GNU Guix --- Functional package management for GNU
- ;;; Copyright © 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org>
- ;;; Copyright © 2015 David Thompson <davet@gnu.org>
- ;;; Copyright © 2015 Mark H Weaver <mhw@netris.org>
- ;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
- ;;; Copyright © 2019 Guillaume Le Vaillant <glv@posteo.net>
- ;;; Copyright © 2020 Julien Lepiller <julien@lepiller.eu>
- ;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
- ;;; Copyright © 2021 Chris Marusich <cmmarusich@gmail.com>
- ;;;
- ;;; This file is part of GNU Guix.
- ;;;
- ;;; GNU Guix is free software; you can redistribute it and/or modify it
- ;;; under the terms of the GNU General Public License as published by
- ;;; the Free Software Foundation; either version 3 of the License, or (at
- ;;; your option) any later version.
- ;;;
- ;;; GNU Guix is distributed in the hope that it will be useful, but
- ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
- ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;;; GNU General Public License for more details.
- ;;;
- ;;; You should have received a copy of the GNU General Public License
- ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
- (define-module (guix build syscalls)
- #:use-module (system foreign)
- #:use-module (system base target)
- #:use-module (rnrs bytevectors)
- #:autoload (ice-9 binary-ports) (get-bytevector-n)
- #:use-module (srfi srfi-1)
- #:use-module (srfi srfi-9)
- #:use-module (srfi srfi-9 gnu)
- #:use-module (srfi srfi-11)
- #:use-module (srfi srfi-19)
- #:use-module (srfi srfi-26)
- #:use-module (ice-9 rdelim)
- #:use-module (ice-9 regex)
- #:use-module (ice-9 match)
- #:use-module (ice-9 ftw)
- #:export (MS_RDONLY
- MS_NOSUID
- MS_NODEV
- MS_NOEXEC
- MS_REMOUNT
- MS_NOATIME
- MS_STRICTATIME
- MS_RELATIME
- MS_BIND
- MS_MOVE
- MS_LAZYTIME
- MNT_FORCE
- MNT_DETACH
- MNT_EXPIRE
- UMOUNT_NOFOLLOW
- restart-on-EINTR
- mount?
- mount-device-number
- mount-source
- mount-point
- mount-type
- mount-options
- mount-flags
- mounts
- mount-points
- swapon
- swapoff
- file-system?
- file-system-type
- file-system-block-size
- file-system-block-count
- file-system-blocks-free
- file-system-blocks-available
- file-system-file-count
- file-system-free-file-nodes
- file-system-identifier
- file-system-maximum-name-length
- file-system-fragment-size
- file-system-mount-flags
- statfs
- ST_RDONLY
- ST_NOSUID
- ST_NODEV
- ST_NOEXEC
- ST_SYNCHRONOUS
- ST_MANDLOCK
- ST_WRITE
- ST_APPEND
- ST_IMMUTABLE
- ST_NOATIME
- ST_NODIRATIME
- ST_RELATIME
- statfs-flags->mount-flags
- free-disk-space
- device-in-use?
- add-to-entropy-count
- processes
- mkdtemp!
- fdatasync
- pivot-root
- scandir*
- getxattr
- setxattr
- fcntl-flock
- lock-file
- unlock-file
- with-file-lock
- with-file-lock/no-wait
- set-thread-name
- thread-name
- CLONE_CHILD_CLEARTID
- CLONE_CHILD_SETTID
- CLONE_NEWNS
- CLONE_NEWUTS
- CLONE_NEWIPC
- CLONE_NEWUSER
- CLONE_NEWPID
- CLONE_NEWNET
- clone
- setns
- PF_PACKET
- AF_PACKET
- all-network-interface-names
- network-interface-names
- network-interface-netmask
- network-interface-running?
- loopback-network-interface?
- arp-network-interface?
- network-interface-address
- set-network-interface-netmask
- set-network-interface-up
- configure-network-interface
- add-network-route/gateway
- delete-network-route
- interface?
- interface-name
- interface-flags
- interface-address
- interface-netmask
- interface-broadcast-address
- network-interfaces
- termios?
- termios-input-flags
- termios-output-flags
- termios-control-flags
- termios-local-flags
- termios-line-discipline
- termios-control-chars
- termios-input-speed
- termios-output-speed
- local-flags
- input-flags
- tcsetattr-action
- tcgetattr
- tcsetattr
- window-size?
- window-size-rows
- window-size-columns
- window-size-x-pixels
- window-size-y-pixels
- terminal-window-size
- terminal-columns
- terminal-rows
- utmpx?
- utmpx-login-type
- utmpx-pid
- utmpx-line
- utmpx-id
- utmpx-user
- utmpx-host
- utmpx-termination-status
- utmpx-exit-status
- utmpx-session-id
- utmpx-time
- utmpx-address
- login-type
- utmpx-entries
- (read-utmpx-from-port . read-utmpx)))
- ;;; Commentary:
- ;;;
- ;;; This module provides bindings to libc's syscall wrappers. It uses the
- ;;; FFI, and thus requires a dynamically-linked Guile.
- ;;;
- ;;; Some syscalls are already defined in statically-linked Guile by applying
- ;;; 'guile-linux-syscalls.patch'.
- ;;;
- ;;; Visibility of syscall's symbols shared between this module and static Guile
- ;;; is a bit delicate. It is handled by 'define-as-needed' macro.
- ;;;
- ;;; This macro is used to export symbols in dynamic Guile context, and to
- ;;; re-export them in static Guile context.
- ;;;
- ;;; This way, even if they don't appear in #:export list, it is safe to use
- ;;; syscalls from this module in static or dynamic Guile context.
- ;;;
- ;;; Code:
- ;;;
- ;;; Packed structures.
- ;;;
- (define-syntax sizeof*
- ;; XXX: This duplicates 'compile-time-value'.
- (syntax-rules (int128 array)
- ((_ int128)
- 16)
- ((_ (array type n))
- (* (sizeof* type) n))
- ((_ type)
- (let-syntax ((v (lambda (s)
- ;; When compiling natively, call 'sizeof' at expansion
- ;; time; otherwise, emit code to call it at run time.
- (syntax-case s ()
- (_
- (if (= (target-word-size)
- (with-target %host-type target-word-size))
- (sizeof type)
- #'(sizeof type)))))))
- v))))
- (define-syntax alignof*
- ;; XXX: This duplicates 'compile-time-value'.
- (syntax-rules (int128 array)
- ((_ int128)
- 16)
- ((_ (array type n))
- (alignof* type))
- ((_ type)
- (let-syntax ((v (lambda (s)
- ;; When compiling natively, call 'sizeof' at expansion
- ;; time; otherwise, emit code to call it at run time.
- (syntax-case s ()
- (_
- (if (= (target-word-size)
- (with-target %host-type target-word-size))
- (alignof type)
- #'(alignof type)))))))
- v))))
- (define-syntax align ;as found in (system foreign)
- (syntax-rules (~)
- "Add to OFFSET whatever it takes to get proper alignment for TYPE."
- ((_ offset (type ~ endianness))
- (align offset type))
- ((_ offset type)
- (1+ (logior (1- offset) (1- (alignof* type)))))))
- (define-syntax type-size
- (syntax-rules (~)
- ((_ (type ~ order))
- (sizeof* type))
- ((_ type)
- (sizeof* type))))
- (define-syntax struct-alignment
- (syntax-rules ()
- "Compute the alignment for the aggregate made of TYPES at OFFSET. The
- result is the alignment of the \"most strictly aligned component\"."
- ((_ offset types ...)
- (max (align offset types) ...))))
- (define-syntax struct-size
- (syntax-rules ()
- "Return the size in bytes of the structure made of TYPES."
- ((_ offset (types-processed ...))
- ;; The SysV ABI P.S. says: "Aggregates (structures and arrays) and unions
- ;; assume the alignment of their most strictly aligned component." As an
- ;; example, a struct such as "int32, int16" has size 8, not 6.
- (1+ (logior (1- offset)
- (1- (struct-alignment offset types-processed ...)))))
- ((_ offset (types-processed ...) type0 types ...)
- (struct-size (+ (type-size type0) (align offset type0))
- (type0 types-processed ...)
- types ...))))
- (define-syntax write-type
- (syntax-rules (~ array *)
- ((_ bv offset (type ~ order) value)
- (bytevector-uint-set! bv offset value
- (endianness order) (sizeof* type)))
- ((_ bv offset (array type n) value)
- (let loop ((i 0)
- (value value)
- (o offset))
- (unless (= i n)
- (match value
- ((head . tail)
- (write-type bv o type head)
- (loop (+ 1 i) tail (+ o (sizeof* type))))))))
- ((_ bv offset '* value)
- (bytevector-uint-set! bv offset (pointer-address value)
- (native-endianness) (sizeof* '*)))
- ((_ bv offset type value)
- (bytevector-uint-set! bv offset value
- (native-endianness) (sizeof* type)))))
- (define-syntax write-types
- (syntax-rules ()
- ((_ bv offset () ())
- #t)
- ((_ bv offset (type0 types ...) (field0 fields ...))
- (begin
- (write-type bv (align offset type0) type0 field0)
- (write-types bv
- (+ (align offset type0) (type-size type0))
- (types ...) (fields ...))))))
- (define-syntax read-type
- (syntax-rules (~ array quote *)
- ((_ bv offset '*)
- (make-pointer (bytevector-uint-ref bv offset
- (native-endianness)
- (sizeof* '*))))
- ((_ bv offset (type ~ order))
- (bytevector-uint-ref bv offset
- (endianness order) (sizeof* type)))
- ((_ bv offset (array type n))
- (unfold (lambda (i) (= i n))
- (lambda (i)
- (read-type bv (+ offset (* i (sizeof* type))) type))
- 1+
- 0))
- ((_ bv offset type)
- (bytevector-uint-ref bv offset
- (native-endianness) (sizeof* type)))))
- (define-syntax read-types
- (syntax-rules ()
- ((_ return bv offset () (values ...))
- (return values ...))
- ((_ return bv offset (type0 types ...) (values ...))
- (read-types return
- bv
- (+ (align offset type0) (type-size type0))
- (types ...)
- (values ... (read-type bv
- (align offset type0)
- type0))))))
- (define-syntax define-c-struct-macro
- (syntax-rules ()
- "Define NAME as a macro that can be queried to get information about the C
- struct it represents. In particular:
- (NAME field-offset FIELD)
- returns the offset in bytes of FIELD within the C struct represented by NAME."
- ((_ name ((fields types) ...))
- (define-c-struct-macro name
- (fields ...) 0 ()
- ((fields types) ...)))
- ((_ name (fields ...) offset (clauses ...) ((field type) rest ...))
- (define-c-struct-macro name
- (fields ...)
- (+ (align offset type) (type-size type))
- (clauses ... ((_ field-offset field) (align offset type)))
- (rest ...)))
- ((_ name (fields ...) offset (clauses ...) ())
- (define-syntax name
- (syntax-rules (field-offset fields ...)
- clauses ...)))))
- (define-syntax define-c-struct
- (syntax-rules ()
- "Define SIZE as the size in bytes of the C structure made of FIELDS. READ
- as a deserializer and WRITE! as a serializer for the C structure with the
- given TYPES. READ uses WRAP-FIELDS to return its value."
- ((_ name size wrap-fields read write! (fields types) ...)
- (begin
- (define-c-struct-macro name
- ((fields types) ...))
- (define size
- (struct-size 0 () types ...))
- (define (write! bv offset fields ...)
- (write-types bv offset (types ...) (fields ...)))
- (define* (read bv #:optional (offset 0))
- (read-types wrap-fields bv offset (types ...) ()))))))
- (define-syntax-rule (c-struct-field-offset type field)
- "Return the offset in BYTES of FIELD within TYPE, where TYPE is a C struct
- defined with 'define-c-struct' and FIELD is a field identifier. An
- expansion-time error is raised if FIELD does not exist in TYPE."
- (type field-offset field))
- ;;;
- ;;; FFI.
- ;;;
- (define (call-with-restart-on-EINTR thunk)
- (let loop ()
- (catch 'system-error
- thunk
- (lambda args
- (if (= (system-error-errno args) EINTR)
- (loop)
- (apply throw args))))))
- (define-syntax-rule (restart-on-EINTR expr)
- "Evaluate EXPR and restart upon EINTR. Return the value of EXPR."
- (call-with-restart-on-EINTR (lambda () expr)))
- (define (syscall->procedure return-type name argument-types)
- "Return a procedure that wraps the C function NAME using the dynamic FFI,
- and that returns two values: NAME's return value, and errno.
- If an error occurs while creating the binding, defer the error report until
- the returned procedure is called."
- (catch #t
- (lambda ()
- (let ((ptr (dynamic-func name (dynamic-link))))
- ;; The #:return-errno? facility was introduced in Guile 2.0.12.
- (pointer->procedure return-type ptr argument-types
- #:return-errno? #t)))
- (lambda args
- (lambda _
- (throw 'system-error name "~A" (list (strerror ENOSYS))
- (list ENOSYS))))))
- (define-syntax define-as-needed
- (syntax-rules ()
- "Define VARIABLE. If VARIABLE already exists in (guile) then re-export it,
- otherwise export the newly-defined VARIABLE."
- ((_ (proc args ...) body ...)
- (define-as-needed proc (lambda* (args ...) body ...)))
- ((_ variable value)
- (if (module-defined? the-scm-module 'variable)
- (module-re-export! (current-module) '(variable))
- (begin
- (module-define! (current-module) 'variable value)
- (module-export! (current-module) '(variable)))))))
- ;;;
- ;;; File systems.
- ;;;
- (define (augment-mtab source target type options)
- "Augment /etc/mtab with information about the given mount point."
- (let ((port (open-file "/etc/mtab" "a")))
- (format port "~a ~a ~a ~a 0 0~%"
- source target type (or options "rw"))
- (close-port port)))
- (define (read-mtab port)
- "Read an mtab-formatted file from PORT, returning a list of tuples."
- (let loop ((result '()))
- (let ((line (read-line port)))
- (if (eof-object? line)
- (reverse result)
- (loop (cons (string-tokenize line) result))))))
- (define (remove-from-mtab target)
- "Remove mount point TARGET from /etc/mtab."
- (define entries
- (remove (match-lambda
- ((device mount-point type options freq passno)
- (string=? target mount-point))
- (_ #f))
- (call-with-input-file "/etc/mtab" read-mtab)))
- (call-with-output-file "/etc/mtab"
- (lambda (port)
- (for-each (match-lambda
- ((device mount-point type options freq passno)
- (format port "~a ~a ~a ~a ~a ~a~%"
- device mount-point type options freq passno)))
- entries))))
- ;; Linux mount flags, from libc's <sys/mount.h>.
- (define MS_RDONLY 1)
- (define MS_NOSUID 2)
- (define MS_NODEV 4)
- (define MS_NOEXEC 8)
- (define MS_REMOUNT 32)
- (define MS_NOATIME 1024)
- (define MS_BIND 4096)
- (define MS_MOVE 8192)
- (define MS_RELATIME 2097152)
- (define MS_STRICTATIME 16777216)
- (define MS_LAZYTIME 33554432)
- (define MNT_FORCE 1)
- (define MNT_DETACH 2)
- (define MNT_EXPIRE 4)
- (define UMOUNT_NOFOLLOW 8)
- (define-as-needed (mount source target type
- #:optional (flags 0) options
- #:key (update-mtab? #f))
- "Mount device SOURCE on TARGET as a file system TYPE.
- Optionally, FLAGS may be a bitwise-or of the MS_* <sys/mount.h>
- constants, and OPTIONS may be a string. When FLAGS contains
- MS_REMOUNT, SOURCE and TYPE are ignored. When UPDATE-MTAB? is true,
- update /etc/mtab. Raise a 'system-error' exception on error."
- ;; XXX: '#:update-mtab?' is not implemented by core 'mount'.
- (let ((proc (syscall->procedure int "mount" `(* * * ,unsigned-long *))))
- (let-values (((ret err)
- (proc (if source
- (string->pointer source)
- %null-pointer)
- (string->pointer target)
- (if type
- (string->pointer type)
- %null-pointer)
- flags
- (if options
- (string->pointer options)
- %null-pointer))))
- (unless (zero? ret)
- (throw 'system-error "mount" "mount ~S on ~S: ~A"
- (list source target (strerror err))
- (list err)))
- (when update-mtab?
- (augment-mtab source target type options)))))
- (define-as-needed (umount target
- #:optional (flags 0)
- #:key (update-mtab? #f))
- "Unmount TARGET. Optionally FLAGS may be one of the MNT_* or UMOUNT_*
- constants from <sys/mount.h>."
- ;; XXX: '#:update-mtab?' is not implemented by core 'umount'.
- (let ((proc (syscall->procedure int "umount2" `(* ,int))))
- (let-values (((ret err)
- (proc (string->pointer target) flags)))
- (unless (zero? ret)
- (throw 'system-error "umount" "~S: ~A"
- (list target (strerror err))
- (list err)))
- (when update-mtab?
- (remove-from-mtab target)))))
- ;; Mount point information.
- (define-record-type <mount>
- (%mount source point devno type options)
- mount?
- (devno mount-device-number) ;st_dev
- (source mount-source) ;string
- (point mount-point) ;string
- (type mount-type) ;string
- (options mount-options)) ;string
- (define (option-string->mount-flags str)
- "Parse the \"option string\" STR as it appears in /proc/mounts and similar,
- and return two values: a mount bitmask (inclusive or of MS_* constants), and
- the remaining unprocessed options."
- ;; Why do we need to do this? Because mount flags and mount options are
- ;; often lumped together; this is the case in /proc/mounts & co., so we need
- ;; to extract the bits that actually correspond to mount flags.
- (define not-comma
- (char-set-complement (char-set #\,)))
- (define lst
- (string-tokenize str not-comma))
- (let loop ((options lst)
- (mask 0)
- (remainder '()))
- (match options
- (()
- (values mask (string-concatenate-reverse remainder)))
- ((head . tail)
- (letrec-syntax ((match-options (syntax-rules (=>)
- ((_)
- (loop tail mask
- (cons head remainder)))
- ((_ (str => bit) rest ...)
- (if (string=? str head)
- (loop tail (logior bit mask)
- remainder)
- (match-options rest ...))))))
- (match-options ("rw" => 0)
- ("ro" => MS_RDONLY)
- ("nosuid" => MS_NOSUID)
- ("nodev" => MS_NODEV)
- ("noexec" => MS_NOEXEC)
- ("relatime" => MS_RELATIME)
- ("noatime" => MS_NOATIME)))))))
- (define (mount-flags mount)
- "Return the mount flags of MOUNT, a <mount> record, as an inclusive or of
- MS_* constants."
- (option-string->mount-flags (mount-options mount)))
- (define (octal-decode str)
- "Decode octal escapes from STR and return the corresponding string. STR may
- look like this: \"white\\040space\", which is decoded as \"white space\"."
- (define char-set:octal
- (char-set #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7))
- (define (octal? c)
- (char-set-contains? char-set:octal c))
- (let loop ((chars (string->list str))
- (result '()))
- (match chars
- (()
- (list->string (reverse result)))
- ((#\\ (? octal? a) (? octal? b) (? octal? c) . rest)
- (loop rest
- (cons (integer->char
- (string->number (list->string (list a b c)) 8))
- result)))
- ((head . tail)
- (loop tail (cons head result))))))
- (define (mounts)
- "Return the list of mounts (<mount> records) visible in the namespace of the
- current process."
- (define (string->device-number str)
- (match (string-split str #\:)
- (((= string->number major) (= string->number minor))
- (+ (* major 256) minor))))
- (call-with-input-file "/proc/self/mountinfo"
- (lambda (port)
- (let loop ((result '()))
- (let ((line (read-line port)))
- (if (eof-object? line)
- (reverse result)
- (match (string-tokenize line)
- ;; See the proc(5) man page for a description of the columns.
- ((id parent-id major:minor root mount-point
- options _ ... "-" type source _)
- (let ((devno (string->device-number major:minor)))
- (loop (cons (%mount (octal-decode source)
- (octal-decode mount-point)
- devno type options)
- result)))))))))))
- (define (mount-points)
- "Return the mounts points for currently mounted file systems."
- (map mount-point (mounts)))
- (define swapon
- (let ((proc (syscall->procedure int "swapon" (list '* int))))
- (lambda* (device #:optional (flags 0))
- "Use the block special device at DEVICE for swapping."
- (let-values (((ret err)
- (proc (string->pointer device) flags)))
- (unless (zero? ret)
- (throw 'system-error "swapon" "~S: ~A"
- (list device (strerror err))
- (list err)))))))
- (define swapoff
- (let ((proc (syscall->procedure int "swapoff" '(*))))
- (lambda (device)
- "Stop using block special device DEVICE for swapping."
- (let-values (((ret err) (proc (string->pointer device))))
- (unless (zero? ret)
- (throw 'system-error "swapoff" "~S: ~A"
- (list device (strerror err))
- (list err)))))))
- (define-as-needed RB_AUTOBOOT #x01234567)
- (define-as-needed RB_HALT_SYSTEM #xcdef0123)
- (define-as-needed RB_ENABLED_CAD #x89abcdef)
- (define-as-needed RB_DISABLE_CAD 0)
- (define-as-needed RB_POWER_OFF #x4321fedc)
- (define-as-needed RB_SW_SUSPEND #xd000fce2)
- (define-as-needed RB_KEXEC #x45584543)
- (define-as-needed (reboot #:optional (cmd RB_AUTOBOOT))
- (let ((proc (syscall->procedure int "reboot" (list int))))
- (let-values (((ret err) (proc cmd)))
- (unless (zero? ret)
- (throw 'system-error "reboot" "~S: ~A"
- (list cmd (strerror err))
- (list err))))))
- (define-as-needed (load-linux-module data #:optional (options ""))
- (let ((proc (syscall->procedure int "init_module"
- (list '* unsigned-long '*))))
- (let-values (((ret err)
- (proc (bytevector->pointer data)
- (bytevector-length data)
- (string->pointer options))))
- (unless (zero? ret)
- (throw 'system-error "load-linux-module" "~A"
- (list (strerror err))
- (list err))))))
- (define (kernel? pid)
- "Return #t if PID designates a \"kernel thread\" rather than a normal
- user-land process."
- (let ((stat (call-with-input-file (format #f "/proc/~a/stat" pid)
- (compose string-tokenize read-string))))
- ;; See proc.txt in Linux's documentation for the list of fields.
- (match stat
- ((pid tcomm state ppid pgrp sid tty_nr tty_pgrp flags min_flt
- cmin_flt maj_flt cmaj_flt utime stime cutime cstime
- priority nice num_thread it_real_value start_time
- vsize rss rsslim
- (= string->number start_code) (= string->number end_code) _ ...)
- ;; Got this obscure trick from sysvinit's 'killall5' program.
- (and (zero? start_code) (zero? end_code))))))
- (define (processes)
- "Return the list of live processes."
- (sort (filter-map (lambda (file)
- (let ((pid (string->number file)))
- (and pid
- (not (kernel? pid))
- pid)))
- (scandir "/proc"))
- <))
- (define mkdtemp!
- (let ((proc (syscall->procedure '* "mkdtemp" '(*))))
- (lambda (tmpl)
- "Create a new unique directory in the file system using the template
- string TMPL and return its file name. TMPL must end with 'XXXXXX'."
- (let-values (((result err) (proc (string->pointer tmpl))))
- (when (null-pointer? result)
- (throw 'system-error "mkdtemp!" "~S: ~A"
- (list tmpl (strerror err))
- (list err)))
- (pointer->string result)))))
- (define fdatasync
- (let ((proc (syscall->procedure int "fdatasync" (list int))))
- (lambda (port)
- "Flush buffered output of PORT, an output file port, and then call
- fdatasync(2) on the underlying file descriptor."
- (force-output port)
- (let*-values (((fd) (fileno port))
- ((ret err) (proc fd)))
- (unless (zero? ret)
- (throw 'system-error "fdatasync" "~S: ~A"
- (list fd (strerror err))
- (list err)))))))
- (define-record-type <file-system>
- (file-system type block-size blocks blocks-free
- blocks-available files free-files identifier
- name-length fragment-size mount-flags spare)
- file-system?
- (type file-system-type)
- (block-size file-system-block-size)
- (blocks file-system-block-count)
- (blocks-free file-system-blocks-free)
- (blocks-available file-system-blocks-available)
- (files file-system-file-count)
- (free-files file-system-free-file-nodes)
- (identifier file-system-identifier)
- (name-length file-system-maximum-name-length)
- (fragment-size file-system-fragment-size)
- (mount-flags file-system-mount-flags)
- (spare file-system--spare))
- (define-syntax fsword ;fsword_t
- (identifier-syntax long))
- (define linux? (string-contains %host-type "linux-gnu"))
- (define-syntax define-statfs-flags
- (syntax-rules (linux hurd)
- "Define the statfs mount flags."
- ((_ (name (linux linux-value) (hurd hurd-value)) rest ...)
- (begin
- (define name
- (if linux? linux-value hurd-value))
- (define-statfs-flags rest ...)))
- ((_ (name value) rest ...)
- (begin
- (define name value)
- (define-statfs-flags rest ...)))
- ((_) #t)))
- (define-statfs-flags ;<bits/statfs.h>
- (ST_RDONLY 1)
- (ST_NOSUID 2)
- (ST_NODEV (linux 4) (hurd 0))
- (ST_NOEXEC 8)
- (ST_SYNCHRONOUS 16)
- (ST_MANDLOCK (linux 64) (hurd 0))
- (ST_WRITE (linux 128) (hurd 0))
- (ST_APPEND (linux 256) (hurd 0))
- (ST_IMMUTABLE (linux 512) (hurd 0))
- (ST_NOATIME (linux 1024) (hurd 32))
- (ST_NODIRATIME (linux 2048) (hurd 0))
- (ST_RELATIME (linux 4096) (hurd 64)))
- (define (statfs-flags->mount-flags flags)
- "Convert FLAGS, a logical or of ST_* constants as returned by
- 'file-system-mount-flags', to the corresponding logical or of MS_* constants."
- (letrec-syntax ((match-flags (syntax-rules (=>)
- ((_ (statfs => mount) rest ...)
- (logior (if (zero? (logand flags statfs))
- 0
- mount)
- (match-flags rest ...)))
- ((_)
- 0))))
- (match-flags
- (ST_RDONLY => MS_RDONLY)
- (ST_NOSUID => MS_NOSUID)
- (ST_NODEV => MS_NODEV)
- (ST_NOEXEC => MS_NOEXEC)
- (ST_NOATIME => MS_NOATIME)
- (ST_NODIRATIME => 0) ;FIXME
- (ST_RELATIME => MS_RELATIME))))
- (define-c-struct %statfs ;<bits/statfs.h>
- sizeof-statfs ;slightly overestimated
- file-system
- read-statfs
- write-statfs!
- (type fsword)
- (block-size fsword)
- (blocks uint64)
- (blocks-free uint64)
- (blocks-available uint64)
- (files uint64)
- (free-files uint64)
- (identifier (array int 2))
- (name-length fsword)
- (fragment-size fsword)
- (mount-flags fsword) ;ST_*
- (spare (array fsword 4)))
- (define statfs
- (let ((proc (syscall->procedure int "statfs64" '(* *))))
- (lambda (file)
- "Return a <file-system> data structure describing the file system
- mounted at FILE."
- (let*-values (((stat) (make-bytevector sizeof-statfs))
- ((ret err) (proc (string->pointer file)
- (bytevector->pointer stat))))
- (if (zero? ret)
- (read-statfs stat)
- (throw 'system-error "statfs" "~A: ~A"
- (list file (strerror err))
- (list err)))))))
- (define (free-disk-space file)
- "Return the free disk space, in bytes, on the file system that hosts FILE."
- (let ((fs (statfs file)))
- (* (file-system-block-size fs)
- (file-system-blocks-available fs))))
- ;; Flags for the *at command, notably the 'utime' procedure of libguile.
- ;; From <fcntl.h>.
- (define-as-needed AT_FDCWD -100)
- (define-as-needed AT_SYMLINK_NOFOLLOW #x100)
- (define-as-needed AT_REMOVEDIR #x200)
- (define-as-needed AT_SYMLINK_FOLLOW #x400)
- (define-as-needed AT_NO_AUTOMOUNT #x800)
- (define-as-needed AT_EMPTY_PATH #x1000)
- (define-syntax BLKRRPART ;<sys/mount.h>
- (identifier-syntax #x125F))
- (define* (device-in-use? device)
- "Return #t if the block DEVICE is in use, #f otherwise. This is inspired
- from fdisk_device_is_used function of util-linux. This is particularly useful
- for devices that do not appear in /proc/self/mounts like overlayfs lowerdir
- backend device."
- (let*-values (((fd) (open-fdes device O_RDONLY))
- ((ret err) (%ioctl fd BLKRRPART %null-pointer)))
- (close-fdes fd)
- (cond
- ((= ret 0)
- #f)
- ((= err EBUSY)
- #t)
- ((= err EINVAL)
- ;; We get EINVAL for devices that have the GENHD_FL_NO_PART_SCAN flag
- ;; set in the kernel, in particular loopback devices, though we do seem
- ;; to get it for SCSI storage (/dev/sr0) on QEMU.
- #f)
- (else
- (throw 'system-error "ioctl" "~A"
- (list (strerror err))
- (list err))))))
- (define getxattr
- (let ((proc (syscall->procedure ssize_t "getxattr"
- `(* * * ,size_t))))
- (lambda (file key)
- "Get the extended attribute value for KEY on FILE."
- (let-values (((size err)
- ;; Get size of VALUE for buffer.
- (proc (string->pointer/utf-8 file)
- (string->pointer key)
- (string->pointer "")
- 0)))
- (cond ((< size 0) #f)
- ((zero? size) "")
- ;; Get VALUE in buffer of SIZE. XXX actual size can race.
- (else (let*-values (((buf) (make-bytevector size))
- ((size err)
- (proc (string->pointer/utf-8 file)
- (string->pointer key)
- (bytevector->pointer buf)
- size)))
- (if (>= size 0)
- (utf8->string buf)
- (throw 'system-error "getxattr" "~S: ~A"
- (list file key (strerror err))
- (list err))))))))))
- (define setxattr
- (let ((proc (syscall->procedure int "setxattr"
- `(* * * ,size_t ,int))))
- (lambda* (file key value #:optional (flags 0))
- "Set extended attribute KEY to VALUE on FILE."
- (let*-values (((bv) (string->utf8 value))
- ((ret err)
- (proc (string->pointer/utf-8 file)
- (string->pointer key)
- (bytevector->pointer bv)
- (bytevector-length bv)
- flags)))
- (unless (zero? ret)
- (throw 'system-error "setxattr" "~S: ~A"
- (list file key value (strerror err))
- (list err)))))))
- ;;;
- ;;; Random.
- ;;;
- ;; From <uapi/linux/random.h>.
- (define RNDADDTOENTCNT
- ;; Avoid using %current-system here to avoid depending on host-side code.
- (if (string-prefix? "powerpc64le" %host-type)
- #x80045201
- #x40045201))
- (define (add-to-entropy-count port-or-fd n)
- "Add N to the kernel's entropy count (the value that can be read from
- /proc/sys/kernel/random/entropy_avail). PORT-OR-FD must correspond to
- /dev/urandom or /dev/random. Raise to 'system-error with EPERM when the
- caller lacks root privileges."
- (let ((fd (if (port? port-or-fd)
- (fileno port-or-fd)
- port-or-fd))
- (box (make-bytevector (sizeof int))))
- (bytevector-sint-set! box 0 n (native-endianness)
- (sizeof int))
- (let-values (((ret err)
- (%ioctl fd RNDADDTOENTCNT
- (bytevector->pointer box))))
- (unless (zero? err)
- (throw 'system-error "add-to-entropy-count" "~A"
- (list (strerror err))
- (list err))))))
- ;;;
- ;;; Containers.
- ;;;
- ;; Linux clone flags, from linux/sched.h
- (define CLONE_CHILD_CLEARTID #x00200000)
- (define CLONE_CHILD_SETTID #x01000000)
- (define CLONE_NEWNS #x00020000)
- (define CLONE_NEWUTS #x04000000)
- (define CLONE_NEWIPC #x08000000)
- (define CLONE_NEWUSER #x10000000)
- (define CLONE_NEWPID #x20000000)
- (define CLONE_NEWNET #x40000000)
- (define %set-automatic-finalization-enabled?!
- ;; When using a statically-linked Guile, for instance in the initrd, we
- ;; cannot resolve this symbol, but most of the time we don't need it
- ;; anyway. Thus, delay it.
- (let ((proc (delay
- (pointer->procedure int
- (dynamic-func
- "scm_set_automatic_finalization_enabled"
- (dynamic-link))
- (list int)))))
- (lambda (enabled?)
- "Switch on or off automatic finalization in a separate thread.
- Turning finalization off shuts down the finalization thread as a side effect."
- (->bool ((force proc) (if enabled? 1 0))))))
- (define-syntax-rule (without-automatic-finalization exp)
- "Turn off automatic finalization within the dynamic extent of EXP."
- (let ((enabled? #t))
- (dynamic-wind
- (lambda ()
- (set! enabled? (%set-automatic-finalization-enabled?! #f)))
- (lambda ()
- exp)
- (lambda ()
- (%set-automatic-finalization-enabled?! enabled?)))))
- ;; The libc interface to sys_clone is not useful for Scheme programs, so the
- ;; low-level system call is wrapped instead. The 'syscall' function is
- ;; declared in <unistd.h> as a variadic function; in practice, it expects 6
- ;; pointer-sized arguments, as shown in, e.g., x86_64/syscall.S.
- (define clone
- (let* ((proc (syscall->procedure int "syscall"
- (list long ;sysno
- unsigned-long ;flags
- '* '* '*
- '*)))
- ;; TODO: Don't do this.
- (syscall-id (match (utsname:machine (uname))
- ("i686" 120)
- ("x86_64" 56)
- ("mips64" 5055)
- ("armv7l" 120)
- ("aarch64" 220)
- ("ppc64le" 120)
- (_ #f))))
- (lambda (flags)
- "Create a new child process by duplicating the current parent process.
- Unlike the fork system call, clone accepts FLAGS that specify which resources
- are shared between the parent and child processes."
- (let-values (((ret err)
- ;; Guile 2.2 runs a finalization thread. 'primitive-fork'
- ;; takes care of shutting it down before forking, and we
- ;; must do the same here. Failing to do that, if the
- ;; child process calls 'primitive-fork', it will hang
- ;; while trying to pthread_join the finalization thread
- ;; since that thread does not exist.
- (without-automatic-finalization
- (proc syscall-id flags
- %null-pointer ;child stack
- %null-pointer %null-pointer ;ptid & ctid
- %null-pointer)))) ;unused
- (if (= ret -1)
- (throw 'system-error "clone" "~d: ~A"
- (list flags (strerror err))
- (list err))
- ret)))))
- (define setns
- ;; Some systems may be using an old (pre-2.14) version of glibc where there
- ;; is no 'setns' function available.
- (false-if-exception
- (let ((proc (syscall->procedure int "setns" (list int int))))
- (lambda (fdes nstype)
- "Reassociate the current process with the namespace specified by FDES, a
- file descriptor obtained by opening a /proc/PID/ns/* file. NSTYPE specifies
- which type of namespace the current process may be reassociated with, or 0 if
- there is no such limitation."
- (let-values (((ret err) (proc fdes nstype)))
- (unless (zero? ret)
- (throw 'system-error "setns" "~d ~d: ~A"
- (list fdes nstype (strerror err))
- (list err))))))))
- (define pivot-root
- (let ((proc (syscall->procedure int "pivot_root" (list '* '*))))
- (lambda (new-root put-old)
- "Change the root file system to NEW-ROOT and move the current root file
- system to PUT-OLD."
- (let-values (((ret err)
- (proc (string->pointer new-root)
- (string->pointer put-old))))
- (unless (zero? ret)
- (throw 'system-error "pivot_root" "~S ~S: ~A"
- (list new-root put-old (strerror err))
- (list err)))))))
- ;;;
- ;;; Opendir & co.
- ;;;
- (define (file-type->symbol type)
- ;; Convert TYPE to symbols like 'stat:type' does.
- (cond ((= type DT_REG) 'regular)
- ((= type DT_LNK) 'symlink)
- ((= type DT_DIR) 'directory)
- ((= type DT_FIFO) 'fifo)
- ((= type DT_CHR) 'char-special)
- ((= type DT_BLK) 'block-special)
- ((= type DT_SOCK) 'socket)
- (else 'unknown)))
- ;; 'struct dirent64' for GNU/Linux.
- (define-c-struct %struct-dirent-header/linux
- sizeof-dirent-header/linux
- (lambda (inode offset length type name)
- `((type . ,(file-type->symbol type))
- (inode . ,inode)))
- read-dirent-header/linux
- write-dirent-header!/linux
- (inode int64)
- (offset int64)
- (length unsigned-short)
- (type uint8)
- (name uint8)) ;first byte of 'd_name'
- ;; 'struct dirent64' for GNU/Hurd.
- (define-c-struct %struct-dirent-header/hurd
- sizeof-dirent-header/hurd
- (lambda (inode length type name-length name)
- `((type . ,(file-type->symbol type))
- (inode . ,inode)))
- read-dirent-header/hurd
- write-dirent-header!/hurd
- (inode int64)
- (length unsigned-short)
- (type uint8)
- (namelen uint8)
- (name uint8))
- ;; Constants for the 'type' field, from <dirent.h>.
- (define DT_UNKNOWN 0)
- (define DT_FIFO 1)
- (define DT_CHR 2)
- (define DT_DIR 4)
- (define DT_BLK 6)
- (define DT_REG 8)
- (define DT_LNK 10)
- (define DT_SOCK 12)
- (define DT_WHT 14)
- (define string->pointer/utf-8
- (cut string->pointer <> "UTF-8"))
- (define pointer->string/utf-8
- (cut pointer->string <> <> "UTF-8"))
- (define opendir*
- (let ((proc (syscall->procedure '* "opendir" '(*))))
- (lambda* (name #:optional (string->pointer string->pointer/utf-8))
- (let-values (((ptr err)
- (proc (string->pointer name))))
- (if (null-pointer? ptr)
- (throw 'system-error "opendir*"
- "~A: ~A" (list name (strerror err))
- (list err))
- ptr)))))
- (define closedir*
- (let ((proc (syscall->procedure int "closedir" '(*))))
- (lambda (directory)
- (let-values (((ret err)
- (proc directory)))
- (unless (zero? ret)
- (throw 'system-error "closedir"
- "closedir: ~A" (list (strerror err))
- (list err)))))))
- (define (readdir-procedure name-field-offset sizeof-dirent-header
- read-dirent-header)
- (let ((proc (syscall->procedure '* "readdir64" '(*))))
- (lambda* (directory #:optional (pointer->string pointer->string/utf-8))
- (let ((ptr (proc directory)))
- (and (not (null-pointer? ptr))
- (cons (pointer->string
- (make-pointer (+ (pointer-address ptr) name-field-offset))
- -1)
- (read-dirent-header
- (pointer->bytevector ptr sizeof-dirent-header))))))))
- (define readdir*
- ;; Decide at run time which one must be used.
- (if (string-contains %host-type "linux-gnu")
- (readdir-procedure (c-struct-field-offset %struct-dirent-header/linux
- name)
- sizeof-dirent-header/linux
- read-dirent-header/linux)
- (readdir-procedure (c-struct-field-offset %struct-dirent-header/hurd
- name)
- sizeof-dirent-header/hurd
- read-dirent-header/hurd)))
- (define* (scandir* name #:optional
- (select? (const #t))
- (entry<? (lambda (entry1 entry2)
- (match entry1
- ((name1 . _)
- (match entry2
- ((name2 . _)
- (string<? name1 name2)))))))
- #:key
- (string->pointer string->pointer/utf-8)
- (pointer->string pointer->string/utf-8))
- "This procedure improves on Guile's 'scandir' procedure in several ways:
- 1. Systematically encode decode file names using STRING->POINTER and
- POINTER->STRING (UTF-8 by default; this works around a defect in Guile 2.0/2.2
- where 'scandir' decodes file names according to the current locale, which is
- not always desirable.
- 2. Each entry that is returned has the form (NAME . PROPERTIES).
- PROPERTIES is an alist showing additional properties about the entry, as
- found in 'struct dirent'. An entry may look like this:
- (\"foo.scm\" (type . regular) (inode . 123456))
- Callers must be prepared to deal with the case where 'type' is 'unknown'
- since some file systems do not provide that information.
- 3. Raise to 'system-error' when NAME cannot be opened."
- (let ((directory (opendir* name string->pointer)))
- (dynamic-wind
- (const #t)
- (lambda ()
- (let loop ((result '()))
- (match (readdir* directory pointer->string)
- (#f
- (sort result entry<?))
- (entry
- (loop (if (select? entry)
- (cons entry result)
- result))))))
- (lambda ()
- (closedir* directory)))))
- ;;;
- ;;; Advisory file locking.
- ;;;
- (define-c-struct %struct-flock ;<fcntl.h>
- sizeof-flock
- list
- read-flock
- write-flock!
- (type short)
- (whence short)
- (start size_t)
- (length size_t)
- (pid int))
- (define F_SETLKW
- ;; On Linux-based systems, this is usually 7, but not always
- ;; (exceptions include SPARC.) On GNU/Hurd, it's 9.
- (cond ((string-contains %host-type "sparc") 9) ; sparc-*-linux-gnu
- ((string-contains %host-type "linux") 7) ; *-linux-gnu
- (else 9))) ; *-gnu*
- (define F_SETLK
- ;; Likewise: GNU/Hurd and SPARC use 8, while the others typically use 6.
- (cond ((string-contains %host-type "sparc") 8) ; sparc-*-linux-gnu
- ((string-contains %host-type "linux") 6) ; *-linux-gnu
- (else 8))) ; *-gnu*
- (define F_xxLCK
- ;; The F_RDLCK, F_WRLCK, and F_UNLCK constants.
- (cond ((string-contains %host-type "sparc") #(1 2 3)) ; sparc-*-linux-gnu
- ((string-contains %host-type "hppa") #(1 2 3)) ; hppa-*-linux-gnu
- ((string-contains %host-type "linux") #(0 1 2)) ; *-linux-gnu
- (else #(1 2 3)))) ; *-gnu*
- (define fcntl-flock
- (let ((proc (syscall->procedure int "fcntl" `(,int ,int *))))
- (lambda* (fd-or-port operation #:key (wait? #t))
- "Perform locking OPERATION on the file beneath FD-OR-PORT. OPERATION
- must be a symbol, one of 'read-lock, 'write-lock, or 'unlock. When WAIT? is
- true, block until the lock is acquired; otherwise, thrown an 'flock-error'
- exception if it's already taken."
- (define (operation->int op)
- (case op
- ((read-lock) (vector-ref F_xxLCK 0))
- ((write-lock) (vector-ref F_xxLCK 1))
- ((unlock) (vector-ref F_xxLCK 2))
- (else (error "invalid fcntl-flock operation" op))))
- (define fd
- (if (port? fd-or-port)
- (fileno fd-or-port)
- fd-or-port))
- (define bv
- (make-bytevector sizeof-flock))
- (write-flock! bv 0
- (operation->int operation) SEEK_SET
- 0 0 ;whole file
- 0)
- ;; XXX: 'fcntl' is a vararg function, but here we happily use the
- ;; standard ABI; crossing fingers.
- (let-values (((ret err)
- (proc fd
- (if wait?
- F_SETLKW ;lock & wait
- F_SETLK) ;non-blocking attempt
- (bytevector->pointer bv))))
- (unless (zero? ret)
- ;; Presumably we got EAGAIN or so.
- (throw 'flock-error err))))))
- (define* (lock-file file #:key (wait? #t))
- "Wait and acquire an exclusive lock on FILE. Return an open port."
- (let ((port (open-file file "w0")))
- (fcntl-flock port 'write-lock #:wait? wait?)
- port))
- (define (unlock-file port)
- "Unlock PORT, a port returned by 'lock-file'."
- (fcntl-flock port 'unlock)
- (close-port port)
- #t)
- (define (call-with-file-lock file thunk)
- (let ((port #f))
- (dynamic-wind
- (lambda ()
- (set! port
- (catch 'system-error
- (lambda ()
- (lock-file file))
- (lambda args
- ;; When using the statically-linked Guile in the initrd,
- ;; 'fcntl-flock' returns ENOSYS unconditionally. Ignore
- ;; that error since we're typically the only process running
- ;; at this point.
- (if (= ENOSYS (system-error-errno args))
- #f
- (apply throw args))))))
- thunk
- (lambda ()
- (when port
- (unlock-file port))))))
- (define (call-with-file-lock/no-wait file thunk handler)
- (let ((port #f))
- (dynamic-wind
- (lambda ()
- (set! port
- (catch #t
- (lambda ()
- (lock-file file #:wait? #f))
- (lambda (key . args)
- (match key
- ('flock-error
- (apply handler args)
- ;; No open port to the lock, so return #f.
- #f)
- ('system-error
- ;; When using the statically-linked Guile in the initrd,
- ;; 'fcntl-flock' returns ENOSYS unconditionally. Ignore
- ;; that error since we're typically the only process running
- ;; at this point.
- (if (= ENOSYS (system-error-errno (cons key args)))
- #f
- (apply throw key args)))
- (_ (apply throw key args)))))))
- thunk
- (lambda ()
- (when port
- (unlock-file port))))))
- (define-syntax-rule (with-file-lock file exp ...)
- "Wait to acquire a lock on FILE and evaluate EXP in that context."
- (call-with-file-lock file (lambda () exp ...)))
- (define-syntax-rule (with-file-lock/no-wait file handler exp ...)
- "Try to acquire a lock on FILE and evaluate EXP in that context. Execute
- handler if the lock is already held by another process."
- (call-with-file-lock/no-wait file (lambda () exp ...) handler))
- ;;;
- ;;; Miscellaneous, aka. 'prctl'.
- ;;;
- (define %prctl
- ;; Should it win the API contest against 'ioctl'? You tell us!
- (syscall->procedure int "prctl"
- (list int unsigned-long unsigned-long
- unsigned-long unsigned-long)))
- (define PR_SET_NAME 15) ;<linux/prctl.h>
- (define PR_GET_NAME 16)
- (define %max-thread-name-length
- ;; Maximum length in bytes of the process name, including the terminating
- ;; zero.
- 16)
- (define (set-thread-name!/linux name)
- "Set the name of the calling thread to NAME. NAME is truncated to 15
- bytes."
- (let ((ptr (string->pointer name)))
- (let-values (((ret err)
- (%prctl PR_SET_NAME
- (pointer-address ptr) 0 0 0)))
- (unless (zero? ret)
- (throw 'set-process-name "set-process-name"
- "set-process-name: ~A"
- (list (strerror err))
- (list err))))))
- (define (thread-name/linux)
- "Return the name of the calling thread as a string."
- (let ((buf (make-bytevector %max-thread-name-length)))
- (let-values (((ret err)
- (%prctl PR_GET_NAME
- (pointer-address (bytevector->pointer buf))
- 0 0 0)))
- (if (zero? ret)
- (bytes->string (bytevector->u8-list buf))
- (throw 'process-name "process-name"
- "process-name: ~A"
- (list (strerror err))
- (list err))))))
- (define set-thread-name
- (if (string-contains %host-type "linux")
- set-thread-name!/linux
- (const #f)))
- (define thread-name
- (if (string-contains %host-type "linux")
- thread-name/linux
- (const "")))
- ;;;
- ;;; Network interfaces.
- ;;;
- (define SIOCGIFCONF ;from <bits/ioctls.h>
- ; <net/if.h>
- ; <hurd/ioctl.h>
- (if (string-contains %host-type "linux")
- #x8912 ;GNU/Linux
- #xf00801a4)) ;GNU/Hurd
- (define SIOCGIFFLAGS
- (if (string-contains %host-type "linux")
- #x8913 ;GNU/Linux
- #xc4804191)) ;GNU/Hurd
- (define SIOCSIFFLAGS
- (if (string-contains %host-type "linux")
- #x8914 ;GNU/Linux
- #x84804190)) ;GNU/Hurd
- (define SIOCGIFADDR
- (if (string-contains %host-type "linux")
- #x8915 ;GNU/Linux
- #xc08401a1)) ;GNU/Hurd
- (define SIOCSIFADDR
- (if (string-contains %host-type "linux")
- #x8916 ;GNU/Linux
- #x8084018c)) ;GNU/Hurd
- (define SIOCGIFNETMASK
- (if (string-contains %host-type "linux")
- #x891b ;GNU/Linux
- #xc08401a5)) ;GNU/Hurd
- (define SIOCSIFNETMASK
- (if (string-contains %host-type "linux")
- #x891c ;GNU/Linux
- #x80840196)) ;GNU/Hurd
- (define SIOCADDRT
- (if (string-contains %host-type "linux")
- #x890B ;GNU/Linux
- -1)) ;FIXME: GNU/Hurd?
- (define SIOCDELRT
- (if (string-contains %host-type "linux")
- #x890C ;GNU/Linux
- -1)) ;FIXME: GNU/Hurd?
- ;; Flags and constants from <net/if.h>.
- (define-as-needed IFF_UP #x1) ;Interface is up
- (define-as-needed IFF_BROADCAST #x2) ;Broadcast address valid.
- (define-as-needed IFF_LOOPBACK #x8) ;Is a loopback net.
- (define-as-needed IFF_RUNNING #x40) ;interface RFC2863 OPER_UP
- (define-as-needed IFF_NOARP #x80) ;ARP disabled or unsupported
- (define IF_NAMESIZE 16) ;maximum interface name size
- (define-c-struct %ifconf-struct
- sizeof-ifconf
- list
- read-ifconf
- write-ifconf!
- (length int) ;int ifc_len
- (request '*)) ;struct ifreq *ifc_ifcu
- (define ifreq-struct-size
- ;; 'struct ifreq' begins with an array of IF_NAMESIZE bytes containing the
- ;; interface name (nul-terminated), followed by a bunch of stuff. This is
- ;; its size in bytes.
- (if (= 8 (sizeof '*))
- 40
- 32))
- (define-c-struct sockaddr-in/linux ;<linux/in.h>
- sizeof-sockaddr-in/linux
- (lambda (family port address)
- (make-socket-address family address port))
- read-sockaddr-in/linux
- write-sockaddr-in!/linux
- (family unsigned-short)
- (port (int16 ~ big))
- (address (int32 ~ big)))
- (define-c-struct sockaddr-in/hurd ;<netinet/in.h>
- sizeof-sockaddr-in/hurd
- (lambda (len family port address zero)
- (make-socket-address family address port))
- read-sockaddr-in/hurd
- write-sockaddr-in!/hurd
- (len uint8)
- (family uint8)
- (port (int16 ~ big))
- (address (int32 ~ big))
- (zero (array uint8 8)))
- (define-c-struct sockaddr-in6/linux ;<linux/in6.h>
- sizeof-sockaddr-in6/linux
- (lambda (family port flowinfo address scopeid)
- (make-socket-address family address port flowinfo scopeid))
- read-sockaddr-in6/linux
- write-sockaddr-in6!/linux
- (family unsigned-short)
- (port (int16 ~ big))
- (flowinfo (int32 ~ big))
- (address (int128 ~ big))
- (scopeid int32))
- (define-c-struct sockaddr-in6/hurd ;<netinet/in.h>
- sizeof-sockaddr-in6/hurd
- (lambda (len family port flowinfo address scopeid)
- (make-socket-address family address port flowinfo scopeid))
- read-sockaddr-in6/hurd
- write-sockaddr-in6!/hurd
- (len uint8)
- (family uint8)
- (port (int16 ~ big))
- (flowinfo (int32 ~ big))
- (address (int128 ~ big))
- (scopeid int32))
- (define (write-socket-address!/linux sockaddr bv index)
- "Write SOCKADDR, a socket address as returned by 'make-socket-address', to
- bytevector BV at INDEX."
- (let ((family (sockaddr:fam sockaddr)))
- (cond ((= family AF_INET)
- (write-sockaddr-in!/linux bv index
- family
- (sockaddr:port sockaddr)
- (sockaddr:addr sockaddr)))
- ((= family AF_INET6)
- (write-sockaddr-in6!/linux bv index
- family
- (sockaddr:port sockaddr)
- (sockaddr:flowinfo sockaddr)
- (sockaddr:addr sockaddr)
- (sockaddr:scopeid sockaddr)))
- (else
- (error "unsupported socket address" sockaddr)))))
- (define (write-socket-address!/hurd sockaddr bv index)
- "Write SOCKADDR, a socket address as returned by 'make-socket-address', to
- bytevector BV at INDEX."
- (let ((family (sockaddr:fam sockaddr)))
- (cond ((= family AF_INET)
- (write-sockaddr-in!/hurd bv index
- sizeof-sockaddr-in/hurd
- family
- (sockaddr:port sockaddr)
- (sockaddr:addr sockaddr)
- '(0 0 0 0 0 0 0 0)))
- ((= family AF_INET6)
- (write-sockaddr-in6!/hurd bv index
- sizeof-sockaddr-in6/hurd
- family
- (sockaddr:port sockaddr)
- (sockaddr:flowinfo sockaddr)
- (sockaddr:addr sockaddr)
- (sockaddr:scopeid sockaddr)))
- (else
- (error "unsupported socket address" sockaddr)))))
- (define write-socket-address!
- (if (string-contains %host-type "linux-gnu")
- write-socket-address!/linux
- write-socket-address!/hurd))
- (define PF_PACKET 17) ;<bits/socket.h>
- (define AF_PACKET PF_PACKET)
- (define* (read-socket-address/linux bv #:optional (index 0))
- "Read a socket address from bytevector BV at INDEX."
- (let ((family (bytevector-u16-native-ref bv index)))
- (cond ((= family AF_INET)
- (read-sockaddr-in/linux bv index))
- ((= family AF_INET6)
- (read-sockaddr-in6/linux bv index))
- (else
- ;; XXX: Unsupported address family, such as AF_PACKET. Return a
- ;; vector such that the vector can at least call 'sockaddr:fam'.
- (vector family)))))
- (define* (read-socket-address/hurd bv #:optional (index 0))
- "Read a socket address from bytevector BV at INDEX."
- (let ((family (bytevector-u16-native-ref bv index)))
- (cond ((= family AF_INET)
- (read-sockaddr-in/hurd bv index))
- ((= family AF_INET6)
- (read-sockaddr-in6/hurd bv index))
- (else
- ;; XXX: Unsupported address family, such as AF_PACKET. Return a
- ;; vector such that the vector can at least call 'sockaddr:fam'.
- (vector family)))))
- (define read-socket-address
- (if (string-contains %host-type "linux-gnu")
- read-socket-address/linux
- read-socket-address/hurd))
- (define %ioctl
- ;; The most terrible interface, live from Scheme.
- (syscall->procedure int "ioctl" (list int unsigned-long '*)))
- (define (bytes->string bytes)
- "Read BYTES, a list of bytes, and return the null-terminated string decoded
- from there, or #f if that would be an empty string."
- (match (take-while (negate zero?) bytes)
- (()
- #f)
- (non-zero
- (list->string (map integer->char non-zero)))))
- (define (bytevector->string-list bv stride len)
- "Return the null-terminated strings found in BV every STRIDE bytes. Read at
- most LEN bytes from BV."
- (let loop ((bytes (take (bytevector->u8-list bv)
- (min len (bytevector-length bv))))
- (result '()))
- (match bytes
- (()
- (reverse result))
- (_
- (loop (drop bytes stride)
- (cons (bytes->string bytes) result))))))
- (define* (network-interface-names #:optional sock)
- "Return the names of existing network interfaces. This is typically limited
- to interfaces that are currently up."
- (let* ((close? (not sock))
- (sock (or sock (socket SOCK_STREAM AF_INET 0)))
- (len (* ifreq-struct-size 10))
- (reqs (make-bytevector len))
- (conf (make-bytevector sizeof-ifconf)))
- (write-ifconf! conf 0
- len (bytevector->pointer reqs))
- (let-values (((ret err)
- (%ioctl (fileno sock) SIOCGIFCONF
- (bytevector->pointer conf))))
- (when close?
- (close-port sock))
- (if (zero? ret)
- (bytevector->string-list reqs ifreq-struct-size
- (match (read-ifconf conf)
- ((len . _) len)))
- (throw 'system-error "network-interface-list"
- "network-interface-list: ~A"
- (list (strerror err))
- (list err))))))
- (define %interface-line
- ;; Regexp matching an interface line in Linux's /proc/net/dev.
- (make-regexp "^[[:blank:]]*([[:graph:]]+):.*$"))
- (define (all-network-interface-names)
- "Return all the names of the registered network interfaces, including those
- that are not up."
- (call-with-input-file "/proc/net/dev" ;XXX: Linux-specific
- (lambda (port)
- (let loop ((interfaces '()))
- (let ((line (read-line port)))
- (cond ((eof-object? line)
- (reverse interfaces))
- ((regexp-exec %interface-line line)
- =>
- (lambda (match)
- (loop (cons (match:substring match 1) interfaces))))
- (else
- (loop interfaces))))))))
- (define-as-needed (network-interface-flags socket name)
- "Return a number that is the bit-wise or of 'IFF*' flags for network
- interface NAME."
- (let ((req (make-bytevector ifreq-struct-size)))
- (bytevector-copy! (string->utf8 name) 0 req 0
- (min (string-length name) (- IF_NAMESIZE 1)))
- (let-values (((ret err)
- (%ioctl (fileno socket) SIOCGIFFLAGS
- (bytevector->pointer req))))
- (if (zero? ret)
- ;; The 'ifr_flags' field is IF_NAMESIZE bytes after the
- ;; beginning of 'struct ifreq', and it's a short int.
- (bytevector-sint-ref req IF_NAMESIZE (native-endianness)
- (sizeof short))
- (throw 'system-error "network-interface-flags"
- "network-interface-flags on ~A: ~A"
- (list name (strerror err))
- (list err))))))
- (define (loopback-network-interface? name)
- "Return true if NAME designates a loopback network interface."
- (let* ((sock (socket SOCK_STREAM AF_INET 0))
- (flags (network-interface-flags sock name)))
- (close-port sock)
- (not (zero? (logand flags IFF_LOOPBACK)))))
- (define (network-interface-running? name)
- "Return true if NAME designates a running network interface."
- (let* ((sock (socket SOCK_STREAM AF_INET 0))
- (flags (network-interface-flags sock name)))
- (close-port sock)
- (not (zero? (logand flags IFF_RUNNING)))))
- (define (arp-network-interface? name)
- "Return true if NAME supports the Address Resolution Protocol."
- (let* ((sock (socket SOCK_STREAM AF_INET 0))
- (flags (network-interface-flags sock name)))
- (close-port sock)
- (zero? (logand flags IFF_NOARP))))
- (define-as-needed (set-network-interface-flags socket name flags)
- "Set the flag of network interface NAME to FLAGS."
- (let ((req (make-bytevector ifreq-struct-size)))
- (bytevector-copy! (string->utf8 name) 0 req 0
- (min (string-length name) (- IF_NAMESIZE 1)))
- ;; Set the 'ifr_flags' field.
- (bytevector-uint-set! req IF_NAMESIZE flags (native-endianness)
- (sizeof short))
- (let-values (((ret err)
- (%ioctl (fileno socket) SIOCSIFFLAGS
- (bytevector->pointer req))))
- (unless (zero? ret)
- (throw 'system-error "set-network-interface-flags"
- "set-network-interface-flags on ~A: ~A"
- (list name (strerror err))
- (list err))))))
- (define-as-needed (set-network-interface-address socket name sockaddr)
- "Set the address of network interface NAME to SOCKADDR."
- (let ((req (make-bytevector ifreq-struct-size)))
- (bytevector-copy! (string->utf8 name) 0 req 0
- (min (string-length name) (- IF_NAMESIZE 1)))
- ;; Set the 'ifr_addr' field.
- (write-socket-address! sockaddr req IF_NAMESIZE)
- (let-values (((ret err)
- (%ioctl (fileno socket) SIOCSIFADDR
- (bytevector->pointer req))))
- (unless (zero? ret)
- (throw 'system-error "set-network-interface-address"
- "set-network-interface-address on ~A: ~A"
- (list name (strerror err))
- (list err))))))
- (define (set-network-interface-netmask socket name sockaddr)
- "Set the network mask of interface NAME to SOCKADDR."
- (let ((req (make-bytevector ifreq-struct-size)))
- (bytevector-copy! (string->utf8 name) 0 req 0
- (min (string-length name) (- IF_NAMESIZE 1)))
- ;; Set the 'ifr_addr' field.
- (write-socket-address! sockaddr req IF_NAMESIZE)
- (let-values (((ret err)
- (%ioctl (fileno socket) SIOCSIFNETMASK
- (bytevector->pointer req))))
- (unless (zero? ret)
- (throw 'system-error "set-network-interface-netmask"
- "set-network-interface-netmask on ~A: ~A"
- (list name (strerror err))
- (list err))))))
- (define (network-interface-address socket name)
- "Return the address of network interface NAME. The result is an object of
- the same type as that returned by 'make-socket-address'."
- (let ((req (make-bytevector ifreq-struct-size)))
- (bytevector-copy! (string->utf8 name) 0 req 0
- (min (string-length name) (- IF_NAMESIZE 1)))
- (let-values (((ret err)
- (%ioctl (fileno socket) SIOCGIFADDR
- (bytevector->pointer req))))
- (if (zero? ret)
- (read-socket-address req IF_NAMESIZE)
- (throw 'system-error "network-interface-address"
- "network-interface-address on ~A: ~A"
- (list name (strerror err))
- (list err))))))
- (define (network-interface-netmask socket name)
- "Return the netmask of network interface NAME. The result is an object of
- the same type as that returned by 'make-socket-address'."
- (let ((req (make-bytevector ifreq-struct-size)))
- (bytevector-copy! (string->utf8 name) 0 req 0
- (min (string-length name) (- IF_NAMESIZE 1)))
- (let-values (((ret err)
- (%ioctl (fileno socket) SIOCGIFNETMASK
- (bytevector->pointer req))))
- (if (zero? ret)
- (read-socket-address req IF_NAMESIZE)
- (throw 'system-error "network-interface-netmask"
- "network-interface-netmask on ~A: ~A"
- (list name (strerror err))
- (list err))))))
- (define* (configure-network-interface name sockaddr flags
- #:key netmask)
- "Configure network interface NAME to use SOCKADDR, an address as returned by
- 'make-socket-address', and FLAGS, a bitwise-or of IFF_* constants. If NETMASK
- is true, it must be a socket address to use as the network mask."
- (let ((sock (socket (sockaddr:fam sockaddr) SOCK_STREAM 0)))
- (dynamic-wind
- (const #t)
- (lambda ()
- (set-network-interface-address sock name sockaddr)
- (set-network-interface-flags sock name flags)
- (when netmask
- (set-network-interface-netmask sock name netmask)))
- (lambda ()
- (close-port sock)))))
- (define* (set-network-interface-up name
- #:key (family AF_INET))
- "Turn up the interface NAME."
- (let ((sock (socket family SOCK_STREAM 0)))
- (dynamic-wind
- (const #t)
- (lambda ()
- (let ((flags (network-interface-flags sock name)))
- (set-network-interface-flags sock name
- (logior flags IFF_UP))))
- (lambda ()
- (close-port sock)))))
- ;;;
- ;;; Network routes.
- ;;;
- (define-c-struct %rtentry ;'struct rtentry' from <net/route.h>
- sizeof-rtentry
- list
- read-rtentry
- write-rtentry!
- (pad1 unsigned-long)
- (destination (array uint8 16)) ;struct sockaddr
- (gateway (array uint8 16)) ;struct sockaddr
- (genmask (array uint8 16)) ;struct sockaddr
- (flags unsigned-short)
- (pad2 short)
- (pad3 long)
- (tos uint8)
- (class uint8)
- (pad4 (array uint8 (if (= 8 (sizeof* '*)) 3 1)))
- (metric short)
- (device '*)
- (mtu unsigned-long)
- (window unsigned-long)
- (initial-rtt unsigned-short))
- (define RTF_UP #x0001) ;'rtentry' flags from <net/route.h>
- (define RTF_GATEWAY #x0002)
- (define %sockaddr-any
- (make-socket-address AF_INET INADDR_ANY 0))
- (define add-network-route/gateway
- ;; To allow field names to be matched as literals, we need to move them out
- ;; of the lambda's body since the parameters have the same name. A lot of
- ;; fuss for very little.
- (let-syntax ((gateway-offset (identifier-syntax
- (c-struct-field-offset %rtentry gateway)))
- (destination-offset (identifier-syntax
- (c-struct-field-offset %rtentry destination)))
- (genmask-offset (identifier-syntax
- (c-struct-field-offset %rtentry genmask))))
- (lambda* (socket gateway
- #:key (destination %sockaddr-any) (genmask %sockaddr-any))
- "Add a network route for DESTINATION (a socket address as returned by
- 'make-socket-address') that goes through GATEWAY (a socket address). For
- instance, the call:
- (add-network-route/gateway sock
- (make-socket-address
- AF_INET
- (inet-pton AF_INET \"192.168.0.1\")
- 0))
- is equivalent to this 'net-tools' command:
- route add -net default gw 192.168.0.1
- because the default value of DESTINATION is \"0.0.0.0\"."
- (let ((route (make-bytevector sizeof-rtentry 0)))
- (write-socket-address! gateway route gateway-offset)
- (write-socket-address! destination route destination-offset)
- (write-socket-address! genmask route genmask-offset)
- (bytevector-u16-native-set! route
- (c-struct-field-offset %rtentry flags)
- (logior RTF_UP RTF_GATEWAY))
- (let-values (((ret err)
- (%ioctl (fileno socket) SIOCADDRT
- (bytevector->pointer route))))
- (unless (zero? ret)
- (throw 'system-error "add-network-route/gateway"
- "add-network-route/gateway: ~A"
- (list (strerror err))
- (list err))))))))
- (define delete-network-route
- (let-syntax ((destination-offset (identifier-syntax
- (c-struct-field-offset %rtentry destination))))
- (lambda* (socket destination)
- "Delete the network route for DESTINATION. For instance, the call:
- (delete-network-route sock
- (make-socket-address AF_INET INADDR_ANY 0))
- is equivalent to the 'net-tools' command:
- route del -net default
- "
- (let ((route (make-bytevector sizeof-rtentry 0)))
- (write-socket-address! destination route destination-offset)
- (let-values (((ret err)
- (%ioctl (fileno socket) SIOCDELRT
- (bytevector->pointer route))))
- (unless (zero? ret)
- (throw 'system-error "delete-network-route"
- "delete-network-route: ~A"
- (list (strerror err))
- (list err))))))))
- ;;;
- ;;; Details about network interfaces---aka. 'getifaddrs'.
- ;;;
- ;; Network interfaces. XXX: We would call it <network-interface> but that
- ;; would collide with the ioctl wrappers above.
- (define-record-type <interface>
- (make-interface name flags address netmask broadcast-address)
- interface?
- (name interface-name) ;string
- (flags interface-flags) ;or'd IFF_* values
- (address interface-address) ;sockaddr | #f
- (netmask interface-netmask) ;sockaddr | #f
- (broadcast-address interface-broadcast-address)) ;sockaddr | #f
- (define (write-interface interface port)
- (match interface
- (($ <interface> name flags address)
- (format port "#<interface ~s " name)
- (unless (zero? (logand IFF_UP flags))
- (display "up " port))
- ;; Check whether ADDRESS really is a sockaddr.
- (when address
- (if (member (sockaddr:fam address) (list AF_INET AF_INET6))
- (format port "~a " (inet-ntop (sockaddr:fam address)
- (sockaddr:addr address)))
- (format port "family:~a " (sockaddr:fam address))))
- (format port "~a>" (number->string (object-address interface) 16)))))
- (set-record-type-printer! <interface> write-interface)
- (define (values->interface next name flags address netmask
- broadcast-address data)
- "Given the raw field values passed as arguments, return a pair whose car is
- an <interface> object, and whose cdr is the pointer NEXT."
- (define (maybe-socket-address pointer)
- (if (null-pointer? pointer)
- #f
- (read-socket-address (pointer->bytevector pointer 50)))) ;XXX: size
- (cons (make-interface (if (null-pointer? name)
- #f
- (pointer->string name))
- flags
- (maybe-socket-address address)
- (maybe-socket-address netmask)
- (maybe-socket-address broadcast-address)
- ;; Ignore DATA.
- )
- next))
- (define-c-struct ifaddrs ;<ifaddrs.h>
- %sizeof-ifaddrs
- values->interface
- read-ifaddrs
- write-ifaddrs!
- (next '*)
- (name '*)
- (flags unsigned-int)
- (addr '*)
- (netmask '*)
- (broadcastaddr '*)
- (data '*))
- (define (unfold-interface-list ptr)
- "Call 'read-ifaddrs' on PTR and all its 'next' fields, recursively, and
- return the list of resulting <interface> objects."
- (let loop ((ptr ptr)
- (result '()))
- (if (null-pointer? ptr)
- (reverse result)
- (match (read-ifaddrs (pointer->bytevector ptr %sizeof-ifaddrs))
- ((ifaddr . ptr)
- (loop ptr (cons ifaddr result)))))))
- (define network-interfaces
- (let ((proc (syscall->procedure int "getifaddrs" (list '*))))
- (lambda ()
- "Return a list of <interface> objects, each denoting a configured
- network interface. This is implemented using the 'getifaddrs' libc function."
- (let*-values (((ptr)
- (bytevector->pointer (make-bytevector (sizeof* '*))))
- ((ret err)
- (proc ptr)))
- (if (zero? ret)
- (let* ((ptr (dereference-pointer ptr))
- (result (unfold-interface-list ptr)))
- (free-ifaddrs ptr)
- result)
- (throw 'system-error "network-interfaces" "~A"
- (list (strerror err))
- (list err)))))))
- (define free-ifaddrs
- (syscall->procedure void "freeifaddrs" '(*)))
- ;;;
- ;;; Terminals.
- ;;;
- (define-syntax bits->symbols-body
- (syntax-rules ()
- ((_ bits () ())
- '())
- ((_ bits (name names ...) (value values ...))
- (let ((result (bits->symbols-body bits (names ...) (values ...))))
- (if (zero? (logand bits value))
- result
- (cons 'name result))))))
- (define-syntax define-bits
- (syntax-rules (define)
- "Define the given numerical constants under CONSTRUCTOR, such that
- (CONSTRUCTOR NAME) returns VALUE. Define BITS->SYMBOLS as a procedure that,
- given an integer, returns the list of names of the constants that are or'd."
- ((_ constructor bits->symbols (define names values) ...)
- (begin
- (define-syntax constructor
- (syntax-rules (names ...)
- ((_) 0)
- ((_ names) values) ...
- ((_ first rest (... ...))
- (logior (constructor first) rest (... ...)))))
- (define (bits->symbols bits)
- (bits->symbols-body bits (names ...) (values ...)))))))
- ;; 'local-flags' bits from <bits/termios.h>
- (define-bits local-flags
- local-flags->symbols
- (define ISIG #o0000001)
- (define ICANON #o0000002)
- (define XCASE #o0000004)
- (define ECHO #o0000010)
- (define ECHOE #o0000020)
- (define ECHOK #o0000040)
- (define ECHONL #o0000100)
- (define NOFLSH #o0000200)
- (define TOSTOP #o0000400)
- (define ECHOCTL #o0001000)
- (define ECHOPRT #o0002000)
- (define ECHOKE #o0004000)
- (define FLUSHO #o0010000)
- (define PENDIN #o0040000)
- (define IEXTEN #o0100000)
- (define EXTPROC #o0200000))
- (define-bits input-flags
- input-flags->symbols
- (define IGNBRK #o0000001)
- (define BRKINT #o0000002)
- (define IGNPAR #o0000004)
- (define PARMRK #o0000010)
- (define INPCK #o0000020)
- (define ISTRIP #o0000040)
- (define INLCR #o0000100)
- (define IGNCR #o0000200)
- (define ICRNL #o0000400)
- (define IUCLC #o0001000)
- (define IXON #o0002000)
- (define IXANY #o0004000)
- (define IXOFF #o0010000)
- (define IMAXBEL #o0020000)
- (define IUTF8 #o0040000))
- ;; "Actions" values for 'tcsetattr'.
- (define-bits tcsetattr-action
- %unused-tcsetattr-action->symbols
- (define TCSANOW 0)
- (define TCSADRAIN 1)
- (define TCSAFLUSH 2))
- (define-record-type <termios>
- (termios input-flags output-flags control-flags local-flags
- line-discipline control-chars
- input-speed output-speed)
- termios?
- (input-flags termios-input-flags)
- (output-flags termios-output-flags)
- (control-flags termios-control-flags)
- (local-flags termios-local-flags)
- (line-discipline termios-line-discipline)
- (control-chars termios-control-chars)
- (input-speed termios-input-speed)
- (output-speed termios-output-speed))
- (define-c-struct %termios ;<bits/termios.h>
- sizeof-termios
- termios
- read-termios
- write-termios!
- (input-flags unsigned-int)
- (output-flags unsigned-int)
- (control-flags unsigned-int)
- (local-flags unsigned-int)
- (line-discipline uint8)
- (control-chars (array uint8 32))
- (input-speed unsigned-int)
- (output-speed unsigned-int))
- (define tcgetattr
- (let ((proc (syscall->procedure int "tcgetattr" (list int '*))))
- (lambda (fd)
- "Return the <termios> structure for the tty at FD."
- (let*-values (((bv) (make-bytevector sizeof-termios))
- ((ret err) (proc fd (bytevector->pointer bv))))
- (if (zero? ret)
- (read-termios bv)
- (throw 'system-error "tcgetattr" "~A"
- (list (strerror err))
- (list err)))))))
- (define tcsetattr
- (let ((proc (syscall->procedure int "tcsetattr" (list int int '*))))
- (lambda (fd actions termios)
- "Use TERMIOS for the tty at FD. ACTIONS is one of of the values
- produced by 'tcsetattr-action'; see tcsetattr(3) for details."
- (define bv
- (make-bytevector sizeof-termios))
- (let-syntax ((match/write (syntax-rules ()
- ((_ fields ...)
- (match termios
- (($ <termios> fields ...)
- (write-termios! bv 0 fields ...)))))))
- (match/write input-flags output-flags control-flags local-flags
- line-discipline control-chars input-speed output-speed))
- (let-values (((ret err) (proc fd actions (bytevector->pointer bv))))
- (unless (zero? ret)
- (throw 'system-error "tcgetattr" "~A"
- (list (strerror err))
- (list err)))))))
- (define-syntax TIOCGWINSZ ;<asm-generic/ioctls.h>
- (identifier-syntax #x5413))
- (define-record-type <window-size>
- (window-size rows columns x-pixels y-pixels)
- window-size?
- (rows window-size-rows)
- (columns window-size-columns)
- (x-pixels window-size-x-pixels)
- (y-pixels window-size-y-pixels))
- (define-c-struct winsize ;<bits/ioctl-types.h>
- sizeof-winsize
- window-size
- read-winsize
- write-winsize!
- (rows unsigned-short)
- (columns unsigned-short)
- (x-pixels unsigned-short)
- (y-pixels unsigned-short))
- (define* (terminal-window-size #:optional (port (current-output-port)))
- "Return a <window-size> structure describing the terminal at PORT, or raise
- a 'system-error' if PORT is not backed by a terminal. This procedure
- corresponds to the TIOCGWINSZ ioctl."
- (let*-values (((size) (make-bytevector sizeof-winsize))
- ((ret err) (%ioctl (fileno port) TIOCGWINSZ
- (bytevector->pointer size))))
- (if (zero? ret)
- (read-winsize size)
- (throw 'system-error "terminal-window-size" "~A"
- (list (strerror err))
- (list err)))))
- (define (terminal-dimension window-dimension port fall-back)
- "Return the terminal dimension defined by WINDOW-DIMENSION, one of
- 'window-size-columns' or 'window-size-rows' for PORT. If PORT does not
- correspond to a terminal, return the value returned by FALL-BACK."
- (catch 'system-error
- (lambda ()
- (if (file-port? port)
- (match (window-dimension (terminal-window-size port))
- ;; Things like Emacs shell-mode return 0, which is unreasonable.
- (0 (fall-back))
- ((? number? n) n))
- (fall-back)))
- (lambda args
- (let ((errno (system-error-errno args)))
- ;; ENOTTY is what we're after but 2012-and-earlier Linux versions
- ;; would return EINVAL instead in some cases:
- ;; <https://bugs.ruby-lang.org/issues/10494>.
- ;; Furthermore, some FUSE file systems like unionfs return ENOSYS for
- ;; that ioctl, and bcachefs returns EPERM.
- (if (memv errno (list ENOTTY EINVAL ENOSYS EPERM))
- (fall-back)
- (apply throw args))))))
- (define* (terminal-columns #:optional (port (current-output-port)))
- "Return the best approximation of the number of columns of the terminal at
- PORT, trying to guess a reasonable value if all else fails. The result is
- always a positive integer."
- (define (fall-back)
- (match (and=> (getenv "COLUMNS") string->number)
- (#f 80)
- ((? number? columns)
- (if (> columns 0) columns 80))))
- (terminal-dimension window-size-columns port fall-back))
- (define* (terminal-rows #:optional (port (current-output-port)))
- "Return the best approximation of the number of rows of the terminal at
- PORT, trying to guess a reasonable value if all else fails. The result is
- always a positive integer."
- (terminal-dimension window-size-rows port (const 25)))
- ;;;
- ;;; utmpx.
- ;;;
- (define-record-type <utmpx-entry>
- (utmpx type pid line id user host termination exit
- session time address)
- utmpx?
- (type utmpx-login-type) ;login-type
- (pid utmpx-pid)
- (line utmpx-line) ;device name
- (id utmpx-id)
- (user utmpx-user) ;user name
- (host utmpx-host) ;host name | #f
- (termination utmpx-termination-status)
- (exit utmpx-exit-status)
- (session utmpx-session-id) ;session ID, for windowing
- (time utmpx-time) ;entry time
- (address utmpx-address))
- (define-c-struct %utmpx ;<utmpx.h>
- sizeof-utmpx
- (lambda (type pid line id user host termination exit session
- seconds useconds address %reserved)
- (utmpx type pid
- (bytes->string line) id
- (bytes->string user)
- (bytes->string host) termination exit
- session
- (make-time time-utc (* 1000 useconds) seconds)
- address))
- read-utmpx
- write-utmpx!
- (type short)
- (pid int)
- (line (array uint8 32))
- (id (array uint8 4))
- (user (array uint8 32))
- (host (array uint8 256))
- (termination short)
- (exit short)
- (session int32)
- (time-seconds int32)
- (time-useconds int32)
- (address-v6 (array int32 4))
- (%reserved (array uint8 20)))
- (define-bits login-type
- %unused-login-type->symbols
- (define EMPTY 0) ;No valid user accounting information.
- (define RUN_LVL 1) ;The system's runlevel.
- (define BOOT_TIME 2) ;Time of system boot.
- (define NEW_TIME 3) ;Time after system clock changed.
- (define OLD_TIME 4) ;Time when system clock changed.
- (define INIT_PROCESS 5) ;Process spawned by the init process.
- (define LOGIN_PROCESS 6) ;Session leader of a logged in user.
- (define USER_PROCESS 7) ;Normal process.
- (define DEAD_PROCESS 8) ;Terminated process.
- (define ACCOUNTING 9)) ;System accounting.
- (define setutxent
- (let ((proc (syscall->procedure void "setutxent" '())))
- (lambda ()
- "Open the user accounting database."
- (proc))))
- (define endutxent
- (let ((proc (syscall->procedure void "endutxent" '())))
- (lambda ()
- "Close the user accounting database."
- (proc))))
- (define getutxent
- (let ((proc (syscall->procedure '* "getutxent" '())))
- (lambda ()
- "Return the next entry from the user accounting database."
- (let ((ptr (proc)))
- (if (null-pointer? ptr)
- #f
- (read-utmpx (pointer->bytevector ptr sizeof-utmpx)))))))
- (define (utmpx-entries)
- "Return the list of entries read from the user accounting database."
- (setutxent)
- (let loop ((entries '()))
- (match (getutxent)
- (#f
- (endutxent)
- (reverse entries))
- ((? utmpx? entry)
- (loop (cons entry entries))))))
- (define (read-utmpx-from-port port)
- "Read a utmpx entry from PORT. Return either the EOF object or a utmpx
- entry."
- (match (get-bytevector-n port sizeof-utmpx)
- ((? eof-object? eof)
- eof)
- ((? bytevector? bv)
- (read-utmpx bv))))
- ;;; syscalls.scm ends here
|