Coverage report: /home/ellis/comp/core/std/os.lisp
Kind | Covered | All | % |
expression | 9 | 461 | 2.0 |
branch | 0 | 22 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; std/os.lisp --- OS interop
13
(defparameter *user* (sb-posix:getenv "USER")
14
"The name of the currently logged-in user.")
17
"Return T if effective user is root."
18
(zerop (parse-integer (with-output-to-string (str) (sb-ext:process-output (sb-ext:run-program "id" (list "-u") :search t :output str)) 0))))
20
(defun user-info (&optional (id (sb-posix:getuid)))
21
"USER-INFO returns the password entry for the given name or
22
numerical user ID, as an assoc-list."
23
(multiple-value-bind (name password uid gid gecos home shell)
25
(string (sb-posix:getpwnam id))
26
(integer (sb-posix:getpwuid id)))
27
(declare (ignore password))
29
(list (cons :name name)
34
(cons :shell shell)))))
36
(defun list-all-users ()
37
"List all users via passwd. (uid gid name home shell comment)"
39
(sb-posix:do-passwds (u r)
40
(push (list (sb-posix:passwd-uid u)
41
(sb-posix:passwd-gid u)
42
(sb-posix:passwd-name u)
43
(sb-posix:passwd-dir u)
44
(sb-posix:passwd-shell u)
45
(sb-posix:passwd-gecos u))
49
(defun list-all-groups ()
50
"List all groups. (gid name mem)"
52
(sb-posix:do-groups (g r) (push (list (sb-posix:group-gid g)
53
(sb-posix:group-name g)
54
(sb-posix:group-mem g))
57
;; cat /sys/kernel/cpu_byteorder?
59
(defmacro with-umask (mask &body body)
60
"Temporarily set the system-wide umask for the extent of BODY."
62
`(let ((,umask (sb-posix:umask ,mask)))
63
(unwind-protect (progn ,@body)
64
(sb-posix:umask ,umask)))))
66
;; (with-umask #o22 nil)
68
(defmacro with-fd ((fvar fname &key (flags #.sb-posix:o-rdonly) (close t)) &body body)
69
"Bind FVAR to an open file descriptor resulting from calling SB-POSIX:OPEN on
70
FNAME with FLAGS for the duration of BODY. When CLOSE is non-nil (the default)
71
arrange for FVAR to be closed after BODY."
72
`(let* ((,fvar (sb-posix:open ,fname ,flags)))
73
(unwind-protect (progn ,@body)
74
,@(when close `(sb-posix:close ,fvar)))))
77
;; https://man7.org/linux/man-pages/man3/statvfs.3.html
82
;; https://man7.org/linux/man-pages/man3/getmntent.3.html
83
(define-alien-type mntent
92
(defar setmntent (* t) (filename c-string) (type c-string))
94
(defar getmntent (* t) (stream (* t)))
96
(defar endmntent int (stream (* t)))
98
(defar hasmntopt c-string (mnt (* mntent)) (opt c-string))
100
(defar isatty int (fd int))
102
(defar ("tcgetattr" tcgetattr*) int (fd int) (term (* sb-posix::alien-termios)))
103
(defar ("tcsetattr" tcsetattr*) int (fd int) (actions int) (term (* sb-posix::alien-termios)))
104
(defar cfmakeraw void (term (* sb-posix::alien-termios)))
106
(define-alien-type winsize (struct winsize
109
(xpixel unsigned-short)
110
(ypixel unsigned-short)))
112
;; #define TIOCGWINSZ 0x5413
113
;; #define TIOCSWINSZ 0x5414
114
;; #define TIOCNOTTY 0x5422
115
(defconstant +tiocgwinsz+ #x5413)
116
(defconstant +tiocswinsz+ #x5414)
117
(defconstant +tiocnotty+ #x5422)
118
(defconstant +tcsanow+ 0)
119
(defconstant +tcsadrain+ 1)
120
(defconstant +tcsaflush+ 2)
121
(defconstant +opost+ #x01)
125
;; ref: https://freedesktop.org/wiki/Software/xdg-user-dirs/
126
(defvar *xdg-user-dirs*
127
(let ((tbl (make-hash-table)))
128
(mapc (lambda (x) (setf (gethash (car x) tbl) (cdr x)))
129
'((:desktop . "Desktop")
130
(:download . "Downloads")
131
(:templates . "Templates")
132
(:publicshare . "Public")
133
(:documents . "Documents")
135
(:pictures . "Pictures")
136
(:videos . "Videos")))
139
(defun xdg-user-dir (key)
140
(gethash key *xdg-user-dirs*))
142
(defun (setf xdg-user-dir) (v k)
143
(let ((new (if (typep v 'std/path:absolute-pathname)
145
(merge-pathnames v "~/"))))
146
(setf (gethash k *xdg-user-dirs*) new)))
148
(defun init-xdg-user-dirs ()
149
"Init *XDG-USER-DIRS* from environment."
152
(std/macs:when-let ((e (sb-posix:getenv (concatenate 'string "XDG_" (substitute #\_ #\- (string k)) "DIR"))))
153
(setf (xdg-user-dir k) (pathname e))))
154
(std/hash-table:hash-table-keys *xdg-user-dirs*))
157
;; ref: https://specifications.freedesktop.org/basedir-spec/latest/
158
(defvar *xdg-base-dirs*
159
(let ((tbl (make-hash-table)))
160
(mapc (lambda (x) (setf (gethash (car x) tbl) (cdr x)))
161
`((:data-home . ".data")
162
(:config-home ".config")
163
(:state-home . ".local/state")
164
(:data-dirs . (#p"/usr/local/share/" #p"/usr/share/"))
165
(:config-dirs . (#P"/etc/xdg"))
166
(:cache-home . (".cache"))
170
(defun xdg-base-dir (key) (gethash key *xdg-base-dirs*))
172
(defun (setf xdg-base-dir) (v k)
173
(let ((new (if (typep v 'std/path:absolute-pathname)
175
(merge-pathnames v "~/"))))
176
(setf (gethash k *xdg-base-dirs*) new)))
178
(defun init-xdg-base-dirs ()
179
"Init *XDG-BASE-DIRS* from environment."
182
(std/macs:when-let ((e (sb-posix:getenv (concatenate 'string "XDG_" (substitute #\_ #\- (string k))))))
183
(setf (xdg-base-dir k) (pathname e))))
184
(std/hash-table:hash-table-keys *xdg-base-dirs*)))
187
(defun user-add (name &key shell home comment base gid uid system groups (defaults t) (output t))
188
(let ((useradd (probe-file "/bin/useradd")))
192
,@(when shell `("-s" ,shell))
193
,@(when home `("-d" ,home))
194
,@(when comment `("-c" ,comment))
195
,@(when base `("-b" ,base))
196
,@(when gid `("-g" ,gid))
197
,@(when uid `("-u" ,uid))
198
,@(when system '("-r"))
199
,@(when groups (cons "-g" groups))
200
,@(when defaults '("-D")))
202
(error "unable to find USERADD program (/bin/useradd)"))))
205
(defun group-add (name &key force id users (output t))
206
(let ((groupadd (probe-file "/bin/groupadd")))
210
,@(when force '("-f"))
211
,@(when id `("-i" ,id))
212
,@(when users (cons "-i" users)))
214
(error "unable to find GROUPADD program (/bin/groupadd)"))))
216
;;; with-directory-iterator
217
(defun %get-file-kind (namestring follow-p)
219
(let ((mode (sb-posix:stat-mode
221
(sb-posix:stat namestring)
222
(sb-posix:lstat namestring)))))
223
(case (logand sb-posix:s-ifmt mode)
224
(#.sb-posix:s-ifdir :directory)
225
(#.sb-posix:s-ifchr :character-device)
226
(#.sb-posix:s-ifblk :block-device)
227
(#.sb-posix:s-ifreg :regular-file)
228
(#.sb-posix:s-iflnk :symbolic-link)
229
(#.sb-posix:s-ifsock :socket)
230
(#.sb-posix:s-ififo :pipe)
232
(error "Unknown file mode: ~A." mode))))
233
;; TODO 2025-03-24: test
234
(sb-posix:syscall-error ()
236
;; stat() returned ENOENT: either FILE does not exist
237
;; or the end of the symlink chain is a broken symlink
240
(sb-posix:lstat namestring)
242
(declare (ignorable stat))
243
(values :symbolic-link :broken))))
244
;; lstat() returned ENOENT: FILE does not exist
247
(defun get-file-kind (file follow-p)
248
(%get-file-kind (sb-ext:native-namestring file) follow-p))
250
;;;; Hopefully portable pathname manipulations
251
(defun absolute-pathname-p (pathspec)
252
"Returns T if the PATHSPEC designates an absolute pathname, NIL otherwise."
253
(eq :absolute (car (pathname-directory pathspec))))
255
(defun relative-pathname-p (pathspec)
256
"Returns T if the PATHSPEC designates a relative pathname, NIL otherwise."
257
(not (absolute-pathname-p pathspec)))
259
(defun absolute-pathname (pathspec
260
&optional (default *default-pathname-defaults*))
261
"Returns an absolute pathname corresponding to PATHSPEC by
262
merging it with DEFAULT, and (CURRENT-DIRECTORY) if necessary."
263
(if (relative-pathname-p pathspec)
264
(let ((tmp (merge-pathnames
266
(make-pathname :name nil :type nil :version nil
267
:defaults default))))
268
(if (relative-pathname-p tmp)
269
(merge-pathnames tmp (current-directory))
273
(defun unmerge-pathnames (pathspec
274
&optional (default *default-pathname-defaults*))
275
"Removes those leading directory components from PATHSPEC that
276
are shared with DEFAULT."
277
(let* ((dir (pathname-directory pathspec))
278
(mismatch (mismatch dir (pathname-directory default) :test #'equal)))
279
(make-pathname :directory (when mismatch
280
`(:relative ,@(subseq dir mismatch)))
281
:defaults pathspec)))
283
(defun current-directory ()
284
"CURRENT-DIRECTORY returns the operating system's current
285
directory, which may or may not correspond to
286
*DEFAULT-PATHNAME-DEFAULTS*.
288
SETF CURRENT-DIRECTORY changes the operating system's current
289
directory to the PATHSPEC. An error is signalled if the PATHSPEC
290
is wild or does not designate a directory."
291
(let ((cwd (sb-posix:getcwd)))
293
(pathname (concatenate 'string cwd "/"))
294
(error "Could not get current directory."))))
296
(defun (setf current-directory) (pathspec)
297
"Implicitly set current-directory to PATHSPEC using SB-POSIX:CHDIR."
298
(sb-posix:chdir pathspec))
300
(defun call-with-directory-iterator (pathspec fun)
301
(let ((dir (absolute-pathname (pathname pathspec)))
302
(old-dir (current-directory)))
303
(let ((dp (sb-posix:opendir dir)))
304
(labels ((one-iter ()
305
(let ((dir (sb-posix:readdir dp)))
306
(unless (or (null dir) (null-alien dir))
307
(let ((name (sb-posix:dirent-name dir)))
309
((member name '("." "..") :test #'string=)
311
((eq :directory (%get-file-kind name t))
312
(make-pathname :directory `(:relative ,name)))
314
(let ((dotpos (position #\. name :from-end t)))
315
(if (and dotpos (plusp dotpos))
316
(make-pathname :name (subseq name 0 dotpos)
317
:type (subseq name (1+ dotpos)))
318
(make-pathname :name name))))))))))
320
(let ((*default-pathname-defaults* dir))
321
(setf (current-directory) dir)
322
(funcall fun #'one-iter))
323
(sb-posix:closedir dp)
324
(setf (current-directory) old-dir))))))
326
(defmacro with-directory-iterator ((iterator pathspec) &body body)
327
"PATHSPEC must be a valid directory designator:
328
*DEFAULT-PATHNAME-DEFAULTS* is bound, and (CURRENT-DIRECTORY) is set
329
to the designated directory for the dynamic scope of the body.
331
Within the lexical scope of the body, ITERATOR is defined via
332
macrolet such that successive invocations of (ITERATOR) return
333
the directory entries, one by one. Both files and directories
334
are returned, except '.' and '..'. The order of entries is not
335
guaranteed. The entries are returned as relative pathnames
336
against the designated directory. Entries that are symbolic
337
links are not resolved, but links that point to directories are
338
interpreted as directory designators. Once all entries have been
339
returned, further invocations of (ITERATOR) will all return NIL.
341
The value returned is the value of the last form evaluated in
342
body. Signals an error if PATHSPEC is wild or does not designate
344
(with-gensyms (one-iter)
345
`(call-with-directory-iterator
348
(declare (type function ,one-iter))
349
(macrolet ((,iterator ()
350
`(funcall ,',one-iter)))
353
(defun file-kind (pathspec &key follow-symlinks)
354
"Returns a keyword indicating the kind of file designated by PATHSPEC,
355
or NIL if the file does not exist. Does not follow symbolic
358
Possible file-kinds in addition to NIL are: :REGULAR-FILE,
359
:SYMBOLIC-LINK, :DIRECTORY, :PIPE, :SOCKET, :CHARACTER-DEVICE, and
361
If FOLLOW-SYMLINKS is non-NIL and PATHSPEC designates a broken symlink
362
returns :BROKEN as second value.
364
Signals an error if PATHSPEC is wild."
365
(get-file-kind (merge-pathnames pathspec) follow-symlinks))
367
(defun merge-env-pathnames (path &optional default)
368
(if-let ((%default (sb-posix:getenv default)))
369
(merge-pathnames path (namestring (directory-path %default)))
372
;;; StumpWM exec utils
373
;; from stumpwm/wrappers.lisp
374
(defun execv (program &rest arguments)
375
"Call the system execv() function, replacing the current process image with a
377
(declare (ignorable program arguments))
378
(sb-alien:with-alien ((prg sb-alien:c-string program)
379
(argv (array sb-alien:c-string 256)))
383
do (setf (sb-alien:deref argv j) i))
384
(setf (sb-alien:deref argv (length arguments)) nil)
385
(sb-alien:alien-funcall (sb-alien:extern-alien "execv" (function sb-alien:int sb-alien:c-string (* sb-alien:c-string)))
386
prg (sb-alien:cast argv (* sb-alien:c-string)))))
388
(defun open-pipe (&key (element-type '(unsigned-byte 8)))
389
"Create a pipe and return two fd-streams. The first value is the input
390
stream, and the second value is the output stream."
391
(multiple-value-bind (in-fd out-fd)
393
(let ((in-stream (sb-sys:make-fd-stream in-fd :input t :element-type element-type))
394
(out-stream (sb-sys:make-fd-stream out-fd :output t :element-type element-type)))
395
(values in-stream out-stream))))
397
(defun pathname-executable-p (pathname)
398
"Return T if the pathname describes an executable file."
399
(let ((filename (coerce (sb-ext:native-namestring pathname) 'string)))
400
(and (or (pathname-name pathname)
401
(pathname-type pathname))
402
(sb-unix:unix-access filename sb-unix:x_ok))))
404
;; based on cffi version of set-signal-handler from Andrew Lyon at https://stackoverflow.com/a/10442062
405
;; rewritten to use SBCL's Foreign Function Interface directly by Max-Gerd Retzlaff
406
(defmacro set-signal-handler (signo &body body)
407
`(sb-alien:alien-funcall
408
(sb-alien:extern-alien "signal" (function sb-alien:void
409
sb-alien:int sb-alien:system-area-pointer))
413
(sb-alien::alien-lambda sb-alien:void ((signum sb-alien:int))