123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- ;;;; (fs-at) -- *at FFI bindings for Guile
- ;;;;
- ;;;; Copyright (C) 2021 Maxime Devos <maximedevos at telenet dot be>
- ;;;;
- ;;;; This library is free software; you can redistribute it and/or
- ;;;; modify it under the terms of the GNU Lesser General Public
- ;;;; License as published by the Free Software Foundation; either
- ;;;; version 3 of the License, or (at your option) any later version.
- ;;;;
- ;;;; This library 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
- ;;;; Lesser General Public License for more details.
- ;;;;
- ;;;; You should have received a copy of the GNU Lesser General Public
- ;;;; License along with this library; if not, write to the Free Software
- ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 U>
- (define-module (fs-at)
- #:use-module (srfi srfi-8)
- #:use-module (srfi srfi-9)
- #:use-module (system foreign)
- #:export (make-path-at path-at?)
- #:replace ((new-open . open)
- (new-chmod . chmod)
- (new-chown . chown)
- (new-stat . stat)
- (new-lstat . lstat)
- (new-mkdir . mkdir)
- (new-mknod . mknod)))
- ;; TODO write basic tests for each procedure,
- ;; to make sure the C API is bound correctly
- ;; for each (system, architecture)
- ;; TODO lstat, stat is untested
- ;; TODO only x86-64, Linux is currently supported
- ;; Raw C bindings
- (define libc (dynamic-link))
- ;; XXX check if this is always true.
- (define mode_t int)
- (define dev_t int)
- (define uid_t int)
- (define gid_t int)
- ;; XXX more check non-x86-64, Hurd
- (define ino64_t uint64)
- (define blkcnt64_t uint64)
- (define syscall_ulong_t uint64)
- (define syscall_slong_t int64)
- (define off_t syscall_slong_t)
- (define nlink_t syscall_ulong_t)
- (define time_t syscall_slong_t)
- (define blksize_t syscall_ulong_t)
- ;; XXX Depends on system
- ;; Maybe different on the Hurd
- (define MKNOD_VER 0)
- (define STAT_VER 1)
- (define c:openat
- (pointer->procedure int
- (dynamic-func "openat" libc)
- (list int '* int mode_t)
- #:return-errno? #t))
- (define c:fchmodat
- (pointer->procedure int
- (dynamic-func "fchmodat" libc)
- (list int '* mode_t int)
- #:return-errno? #t))
- (define c:fchownat
- (pointer->procedure int
- (dynamic-func "fchownat" libc)
- (list int '* uid_t gid_t int)
- #:return-errno? #t))
- (define c:mkdirat
- (pointer->procedure int
- (dynamic-func "mkdirat" libc)
- (list int '* mode_t)
- #:return-errno? #t))
- ;; Assumes glibc (and maybe Linux? XXX verify)
- (define c:mknodat
- (let ((c:__xmknodat
- (pointer->procedure int
- (dynamic-func "__xmknodat" libc)
- (list int int '* mode_t '*)
- #:return-errno? #t)))
- (lambda (fd path mode dev)
- (let ((dev-ptr (make-c-struct (list dev_t) (list dev))))
- (c:__xmknodat MKNOD_VER fd path mode dev-ptr)))))
- ;; XXX
- ;; While compiling expression:
- ;; In procedure dynamic-pointer: Symbol not found: fstatat
- (define c:fstatat/1
- (let ((c:__fstatat
- (pointer->procedure int
- (dynamic-func "__fxstatat64" libc)
- (list int int '* '* int)
- #:return-errno? #t)))
- (lambda (fd filename statbuf flags)
- (c:__fstatat STAT_VER fd filename statbuf flags))))
- ;; Scheme wrappers, using ports.
- ;;
- ;; Be careful not to lose fds before the port
- ;; has been created.
- (define (call-with-fd port proc)
- "Call PROC with the fd of PORT."
- ;; call-with-blocked-asyncs:
- ;; prevent leaking file descriptors if interrupted
- (let ((fd (fileno port)))
- (call-with-blocked-asyncs
- (lambda ()
- (dynamic-wind
- (lambda ()
- (set-port-revealed! port
- (+ 1 (port-revealed port))))
- (lambda ()
- (call-with-unblocked-asyncs
- (lambda () (proc fd))))
- (lambda ()
- (release-port-handle port)))))))
- (define-syntax-rule (with-fd port fd exp exp* ...)
- (call-with-fd port (lambda (fd) exp exp* ...)))
- ;; XXX maybe set-port-filename!,
- ;; but be careful with symbolic links
- ;; and relative names (".", "..").
- ;; XXX guile doesn't check for #\0 in filenames,
- ;; verify at #guile whether that is expected.
- ;; (Maybe there are performance reasons.)
- ;; For consistency with guile (and laziness), this module
- ;; reproduces this maybe-a-bug.
- (define (errno-error subr errno)
- (scm-error 'system-error subr (strerror errno) '() (list errno)))
- ;; *at wrappers using Scheme types
- (define (chownat fd-port file-name uid gid flags)
- (with-fd fd-port fd
- (receive (result errno)
- (c:fchownat fd (string->pointer file-name) uid gid flags)
- (unless (= result 0)
- (errno-error "chown" errno)))))
- (define (chmodat fd-port file-name mode flags)
- (with-fd fd-port fd
- (receive (result errno)
- (c:fchmodat fd (string->pointer file-name) mode flags)
- (unless (= result 0)
- (errno-error "chmod" errno)))))
- (define (mode->modes mode)
- (cond ((= mode O_RDWR) "rw0")
- ((= mode O_RDONLY) "r0")
- ((= mode O_WRONLY) "w0")
- ;; What madness is this? XXX what would be appropriate here?
- (#t "r0")))
- ;; Note: the ‘flags’ and ‘mode’ argument order
- ;; difers between openat(2) and chmodat(2).
- (define (openat fd-port file-name flags mode)
- (with-fd fd-port fd
- ((call-with-blocked-asyncs
- (lambda ()
- (receive (result errno)
- (c:openat fd (string->pointer file-name) flags mode)
- (if (>= result 0)
- (let ((port (fdopen result (mode->modes mode))))
- (lambda () port))
- (lambda ()
- (errno-error "open" errno)))))))))
- (define (mkdirat fd-port filename mode)
- (receive (result errno)
- (with-fd fd-port fd
- (c:mkdirat fd (string->pointer filename) mode))
- (unless (= result 0)
- (errno-error "mkdir" errno))))
- (define (mknodat fd-port filename mode dev)
- (receive (result errno)
- (with-fd fd-port fd
- (c:mknodat fd (string->pointer filename) mode dev))
- (unless (= result 0)
- (errno-error "mknod" errno))))
- (define stat64
- (cond ((and (string-prefix? "x86_64-" %host-type)
- (string-contains %host-type "-linux"))
- (list dev_t
- ino64_t nlink_t mode_t
- uid_t gid_t
- int dev_t off_t
- blksize_t blkcnt64_t
- time_t syscall_ulong_t
- time_t syscall_ulong_t
- time_t syscall_ulong_t
- syscall_slong_t syscall_slong_t syscall_slong_t))
- (else ???)))
- (define statbuf->vector
- (let ((for-this-architecture
- (cond ((and (string-prefix? "x86_64-" %host-type)
- (string-contains %host-type "-linux"))
- (lambda (dev
- ino nlink mode
- uid gid
- pad0 rdev size
- blksize blocks
- atime atimensec
- mtime mtimensec
- ctime ctimensec
- reserved0 reserved1 reserved2)
- `#(,dev
- ,ino
- ,mode
- ,nlink
- ,uid
- ,gid
- ,rdev
- ,size
- ,atime
- ,mtime
- ,ctime
- ,blksize
- ,blocks
- ,(mode->type mode)
- ,(mode->perms mode)
- ,atimensec
- ,mtimensec
- ,ctimensec)))
- ;; XXX other architectures and systems
- (#t ???))))
- (lambda (struct-pointer)
- (apply for-this-architecture
- (parse-c-struct struct-pointer stat64)))))
- (define (fstatat fd-port filename flags)
- (let ((statbuf (make-c-struct stat64 (map (const 0) stat64))))
- (receive (result errno)
- (with-fd fd-port fd
- ;; filename = #f/"\0" can be used with AT_EMPTY_PATH
- (c:fstatat/1 fd (string->pointer (or filename ""))
- statbuf flags))
- (if (= result 0)
- (statbuf->vector statbuf)
- (errno-error "stat" errno)))))
- ;; Scheme
- ;;
- ;; XXX the directory should not be closed as long
- ;; as <path-at> objects are used.
- ;;
- ;; Warning: most procedures will still dereference
- ;; the symbolic link (if any)
- (define-record-type <path-at>
- (%make-path-at directory filename)
- path-at?
- (directory path-at-directory)
- (filename path-at-filename))
- (define (make-path-at directory filename)
- ;; XXX verify arguments
- (%make-path-at directory filename))
- (define new-open
- (case-lambda
- ((object flags) (new-open object flags #o666))
- ((object flags mode)
- (if (path-at? object)
- (openat (path-at-directory object) (path-at-filename object)
- flags mode)
- (open object flags mode)))))
- (define (new-chmod object mode)
- (if (path-at? object)
- (chmodat (path-at-directory object) (path-at-filename object)
- mode
- 0)
- (chmod object mode)))
- (define (new-chown object owner group)
- (if (path-at? object)
- (chownat (path-at-directory object) (path-at-filename object)
- owner group
- 0)
- (chown object owner group)))
- (define (new-lstat object)
- (cond ((path-at? object)
- (fstatat (path-at-directory object) (path-at-filename object)
- AT_SYMLINK_NOFOLLOW))
- ((port? object)
- (fstatat object #f (logior AT_EMPTY_PATH AT_SYMLINK_NOFOLLOW)))
- (else (lstat object))))
- (define new-stat
- (case-lambda
- ((object) (new-stat object #t))
- ((object exception-on-error?)
- (display object)
- (if (path-at? object)
- ;; XXX respect exception-on-error?
- (fstatat (path-at-directory object) (path-at-filename object) 0)
- ;; already supports port objects
- (stat object exception-on-error?)))))
- (define new-mkdir
- (case-lambda
- ((object) (new-mkdir object #o777))
- ((object mode)
- (if (path-at? object)
- (mkdirat (path-at-directory object) (path-at-filename object) mode)
- (mkdir object mode)))))
- ;; S_* constants copied from pfinet/linux-src/include/linux/stat.h
- ;; from the hurd-headers package.
- (define S_IFMT #o170000)
- (define S_IFSOCK #o140000)
- (define S_IFLNK #o120000)
- (define S_IFREG #o100000)
- (define S_IFBLK #o060000)
- (define S_IFDIR #o040000)
- (define S_IFCHR #o020000)
- (define S_IFIFO #o010000)
- (define (->mode type perms)
- (logior (case type
- ((regular) S_IFREG)
- ((char-special) S_IFCHR)
- ((block-special) S_IFBLK)
- ((fifo) S_IFIFO)
- ((socket) S_IFSOCK))
- perms))
- (define-syntax-rule (switch obj
- (#:else exp exp* ...)
- (key exp^ exp^* ...) ...)
- (let ((o obj))
- (cond ((eqv? o key) exp^ exp^* ...)
- ...
- (#t exp exp* ...))))
- (define (mode->type mode)
- (switch (logand mode S_IFMT)
- (#:else 'unknown)
- (S_IFSOCK 'socket)
- (S_IFREG 'regular)
- (S_IFLNK 'symlink)
- (S_IFIFO 'fifo)
- (S_IFDIR 'directory)
- (S_IFCHR 'char-special)
- (S_IFBLK 'block-special)))
- (define (mode->perms mode)
- (logand mode #o7777))
- (define (new-mknod object type perms dev)
- (if (path-at? object)
- (mknodat (path-at-directory object) (path-at-filename object)
- (->mode type perms) dev)
- (mknod object type perms dev)))
|