fs-at.scm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. ;;;; (fs-at) -- *at FFI bindings for Guile
  2. ;;;;
  3. ;;;; Copyright (C) 2021 Maxime Devos <maximedevos at telenet dot be>
  4. ;;;;
  5. ;;;; This library is free software; you can redistribute it and/or
  6. ;;;; modify it under the terms of the GNU Lesser General Public
  7. ;;;; License as published by the Free Software Foundation; either
  8. ;;;; version 3 of the License, or (at your option) any later version.
  9. ;;;;
  10. ;;;; This library is distributed in the hope that it will be useful,
  11. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. ;;;; Lesser General Public License for more details.
  14. ;;;;
  15. ;;;; You should have received a copy of the GNU Lesser General Public
  16. ;;;; License along with this library; if not, write to the Free Software
  17. ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 U>
  18. (define-module (fs-at)
  19. #:use-module (srfi srfi-8)
  20. #:use-module (srfi srfi-9)
  21. #:use-module (system foreign)
  22. #:export (make-path-at path-at?)
  23. #:replace ((new-open . open)
  24. (new-chmod . chmod)
  25. (new-chown . chown)
  26. (new-stat . stat)
  27. (new-lstat . lstat)
  28. (new-mkdir . mkdir)
  29. (new-mknod . mknod)))
  30. ;; TODO write basic tests for each procedure,
  31. ;; to make sure the C API is bound correctly
  32. ;; for each (system, architecture)
  33. ;; TODO lstat, stat is untested
  34. ;; TODO only x86-64, Linux is currently supported
  35. ;; Raw C bindings
  36. (define libc (dynamic-link))
  37. ;; XXX check if this is always true.
  38. (define mode_t int)
  39. (define dev_t int)
  40. (define uid_t int)
  41. (define gid_t int)
  42. ;; XXX more check non-x86-64, Hurd
  43. (define ino64_t uint64)
  44. (define blkcnt64_t uint64)
  45. (define syscall_ulong_t uint64)
  46. (define syscall_slong_t int64)
  47. (define off_t syscall_slong_t)
  48. (define nlink_t syscall_ulong_t)
  49. (define time_t syscall_slong_t)
  50. (define blksize_t syscall_ulong_t)
  51. ;; XXX Depends on system
  52. ;; Maybe different on the Hurd
  53. (define MKNOD_VER 0)
  54. (define STAT_VER 1)
  55. (define c:openat
  56. (pointer->procedure int
  57. (dynamic-func "openat" libc)
  58. (list int '* int mode_t)
  59. #:return-errno? #t))
  60. (define c:fchmodat
  61. (pointer->procedure int
  62. (dynamic-func "fchmodat" libc)
  63. (list int '* mode_t int)
  64. #:return-errno? #t))
  65. (define c:fchownat
  66. (pointer->procedure int
  67. (dynamic-func "fchownat" libc)
  68. (list int '* uid_t gid_t int)
  69. #:return-errno? #t))
  70. (define c:mkdirat
  71. (pointer->procedure int
  72. (dynamic-func "mkdirat" libc)
  73. (list int '* mode_t)
  74. #:return-errno? #t))
  75. ;; Assumes glibc (and maybe Linux? XXX verify)
  76. (define c:mknodat
  77. (let ((c:__xmknodat
  78. (pointer->procedure int
  79. (dynamic-func "__xmknodat" libc)
  80. (list int int '* mode_t '*)
  81. #:return-errno? #t)))
  82. (lambda (fd path mode dev)
  83. (let ((dev-ptr (make-c-struct (list dev_t) (list dev))))
  84. (c:__xmknodat MKNOD_VER fd path mode dev-ptr)))))
  85. ;; XXX
  86. ;; While compiling expression:
  87. ;; In procedure dynamic-pointer: Symbol not found: fstatat
  88. (define c:fstatat/1
  89. (let ((c:__fstatat
  90. (pointer->procedure int
  91. (dynamic-func "__fxstatat64" libc)
  92. (list int int '* '* int)
  93. #:return-errno? #t)))
  94. (lambda (fd filename statbuf flags)
  95. (c:__fstatat STAT_VER fd filename statbuf flags))))
  96. ;; Scheme wrappers, using ports.
  97. ;;
  98. ;; Be careful not to lose fds before the port
  99. ;; has been created.
  100. (define (call-with-fd port proc)
  101. "Call PROC with the fd of PORT."
  102. ;; call-with-blocked-asyncs:
  103. ;; prevent leaking file descriptors if interrupted
  104. (let ((fd (fileno port)))
  105. (call-with-blocked-asyncs
  106. (lambda ()
  107. (dynamic-wind
  108. (lambda ()
  109. (set-port-revealed! port
  110. (+ 1 (port-revealed port))))
  111. (lambda ()
  112. (call-with-unblocked-asyncs
  113. (lambda () (proc fd))))
  114. (lambda ()
  115. (release-port-handle port)))))))
  116. (define-syntax-rule (with-fd port fd exp exp* ...)
  117. (call-with-fd port (lambda (fd) exp exp* ...)))
  118. ;; XXX maybe set-port-filename!,
  119. ;; but be careful with symbolic links
  120. ;; and relative names (".", "..").
  121. ;; XXX guile doesn't check for #\0 in filenames,
  122. ;; verify at #guile whether that is expected.
  123. ;; (Maybe there are performance reasons.)
  124. ;; For consistency with guile (and laziness), this module
  125. ;; reproduces this maybe-a-bug.
  126. (define (errno-error subr errno)
  127. (scm-error 'system-error subr (strerror errno) '() (list errno)))
  128. ;; *at wrappers using Scheme types
  129. (define (chownat fd-port file-name uid gid flags)
  130. (with-fd fd-port fd
  131. (receive (result errno)
  132. (c:fchownat fd (string->pointer file-name) uid gid flags)
  133. (unless (= result 0)
  134. (errno-error "chown" errno)))))
  135. (define (chmodat fd-port file-name mode flags)
  136. (with-fd fd-port fd
  137. (receive (result errno)
  138. (c:fchmodat fd (string->pointer file-name) mode flags)
  139. (unless (= result 0)
  140. (errno-error "chmod" errno)))))
  141. (define (mode->modes mode)
  142. (cond ((= mode O_RDWR) "rw0")
  143. ((= mode O_RDONLY) "r0")
  144. ((= mode O_WRONLY) "w0")
  145. ;; What madness is this? XXX what would be appropriate here?
  146. (#t "r0")))
  147. ;; Note: the ‘flags’ and ‘mode’ argument order
  148. ;; difers between openat(2) and chmodat(2).
  149. (define (openat fd-port file-name flags mode)
  150. (with-fd fd-port fd
  151. ((call-with-blocked-asyncs
  152. (lambda ()
  153. (receive (result errno)
  154. (c:openat fd (string->pointer file-name) flags mode)
  155. (if (>= result 0)
  156. (let ((port (fdopen result (mode->modes mode))))
  157. (lambda () port))
  158. (lambda ()
  159. (errno-error "open" errno)))))))))
  160. (define (mkdirat fd-port filename mode)
  161. (receive (result errno)
  162. (with-fd fd-port fd
  163. (c:mkdirat fd (string->pointer filename) mode))
  164. (unless (= result 0)
  165. (errno-error "mkdir" errno))))
  166. (define (mknodat fd-port filename mode dev)
  167. (receive (result errno)
  168. (with-fd fd-port fd
  169. (c:mknodat fd (string->pointer filename) mode dev))
  170. (unless (= result 0)
  171. (errno-error "mknod" errno))))
  172. (define stat64
  173. (cond ((and (string-prefix? "x86_64-" %host-type)
  174. (string-contains %host-type "-linux"))
  175. (list dev_t
  176. ino64_t nlink_t mode_t
  177. uid_t gid_t
  178. int dev_t off_t
  179. blksize_t blkcnt64_t
  180. time_t syscall_ulong_t
  181. time_t syscall_ulong_t
  182. time_t syscall_ulong_t
  183. syscall_slong_t syscall_slong_t syscall_slong_t))
  184. (else ???)))
  185. (define statbuf->vector
  186. (let ((for-this-architecture
  187. (cond ((and (string-prefix? "x86_64-" %host-type)
  188. (string-contains %host-type "-linux"))
  189. (lambda (dev
  190. ino nlink mode
  191. uid gid
  192. pad0 rdev size
  193. blksize blocks
  194. atime atimensec
  195. mtime mtimensec
  196. ctime ctimensec
  197. reserved0 reserved1 reserved2)
  198. `#(,dev
  199. ,ino
  200. ,mode
  201. ,nlink
  202. ,uid
  203. ,gid
  204. ,rdev
  205. ,size
  206. ,atime
  207. ,mtime
  208. ,ctime
  209. ,blksize
  210. ,blocks
  211. ,(mode->type mode)
  212. ,(mode->perms mode)
  213. ,atimensec
  214. ,mtimensec
  215. ,ctimensec)))
  216. ;; XXX other architectures and systems
  217. (#t ???))))
  218. (lambda (struct-pointer)
  219. (apply for-this-architecture
  220. (parse-c-struct struct-pointer stat64)))))
  221. (define (fstatat fd-port filename flags)
  222. (let ((statbuf (make-c-struct stat64 (map (const 0) stat64))))
  223. (receive (result errno)
  224. (with-fd fd-port fd
  225. ;; filename = #f/"\0" can be used with AT_EMPTY_PATH
  226. (c:fstatat/1 fd (string->pointer (or filename ""))
  227. statbuf flags))
  228. (if (= result 0)
  229. (statbuf->vector statbuf)
  230. (errno-error "stat" errno)))))
  231. ;; Scheme
  232. ;;
  233. ;; XXX the directory should not be closed as long
  234. ;; as <path-at> objects are used.
  235. ;;
  236. ;; Warning: most procedures will still dereference
  237. ;; the symbolic link (if any)
  238. (define-record-type <path-at>
  239. (%make-path-at directory filename)
  240. path-at?
  241. (directory path-at-directory)
  242. (filename path-at-filename))
  243. (define (make-path-at directory filename)
  244. ;; XXX verify arguments
  245. (%make-path-at directory filename))
  246. (define new-open
  247. (case-lambda
  248. ((object flags) (new-open object flags #o666))
  249. ((object flags mode)
  250. (if (path-at? object)
  251. (openat (path-at-directory object) (path-at-filename object)
  252. flags mode)
  253. (open object flags mode)))))
  254. (define (new-chmod object mode)
  255. (if (path-at? object)
  256. (chmodat (path-at-directory object) (path-at-filename object)
  257. mode
  258. 0)
  259. (chmod object mode)))
  260. (define (new-chown object owner group)
  261. (if (path-at? object)
  262. (chownat (path-at-directory object) (path-at-filename object)
  263. owner group
  264. 0)
  265. (chown object owner group)))
  266. (define (new-lstat object)
  267. (cond ((path-at? object)
  268. (fstatat (path-at-directory object) (path-at-filename object)
  269. AT_SYMLINK_NOFOLLOW))
  270. ((port? object)
  271. (fstatat object #f (logior AT_EMPTY_PATH AT_SYMLINK_NOFOLLOW)))
  272. (else (lstat object))))
  273. (define new-stat
  274. (case-lambda
  275. ((object) (new-stat object #t))
  276. ((object exception-on-error?)
  277. (display object)
  278. (if (path-at? object)
  279. ;; XXX respect exception-on-error?
  280. (fstatat (path-at-directory object) (path-at-filename object) 0)
  281. ;; already supports port objects
  282. (stat object exception-on-error?)))))
  283. (define new-mkdir
  284. (case-lambda
  285. ((object) (new-mkdir object #o777))
  286. ((object mode)
  287. (if (path-at? object)
  288. (mkdirat (path-at-directory object) (path-at-filename object) mode)
  289. (mkdir object mode)))))
  290. ;; S_* constants copied from pfinet/linux-src/include/linux/stat.h
  291. ;; from the hurd-headers package.
  292. (define S_IFMT #o170000)
  293. (define S_IFSOCK #o140000)
  294. (define S_IFLNK #o120000)
  295. (define S_IFREG #o100000)
  296. (define S_IFBLK #o060000)
  297. (define S_IFDIR #o040000)
  298. (define S_IFCHR #o020000)
  299. (define S_IFIFO #o010000)
  300. (define (->mode type perms)
  301. (logior (case type
  302. ((regular) S_IFREG)
  303. ((char-special) S_IFCHR)
  304. ((block-special) S_IFBLK)
  305. ((fifo) S_IFIFO)
  306. ((socket) S_IFSOCK))
  307. perms))
  308. (define-syntax-rule (switch obj
  309. (#:else exp exp* ...)
  310. (key exp^ exp^* ...) ...)
  311. (let ((o obj))
  312. (cond ((eqv? o key) exp^ exp^* ...)
  313. ...
  314. (#t exp exp* ...))))
  315. (define (mode->type mode)
  316. (switch (logand mode S_IFMT)
  317. (#:else 'unknown)
  318. (S_IFSOCK 'socket)
  319. (S_IFREG 'regular)
  320. (S_IFLNK 'symlink)
  321. (S_IFIFO 'fifo)
  322. (S_IFDIR 'directory)
  323. (S_IFCHR 'char-special)
  324. (S_IFBLK 'block-special)))
  325. (define (mode->perms mode)
  326. (logand mode #o7777))
  327. (define (new-mknod object type perms dev)
  328. (if (path-at? object)
  329. (mknodat (path-at-directory object) (path-at-filename object)
  330. (->mode type perms) dev)
  331. (mknod object type perms dev)))