Compare commits

..

7 Enmetoj

Author SHA1 Message Date
Jaidyn Ann 72b5894823 Update copyright year 2024-05-13 22:15:31 -05:00
Jaidyn Ann 9b2eaa72d3 Fix egg dependency-list 2024-05-13 22:12:06 -05:00
Jaidyn Ann 9e1947b0f7 list-xattrs: Don’t throw exception on empty x-attr 2024-05-13 22:10:39 -05:00
Jaidyn Ann adaa561e59 Formatting, tweaking indentation
… my Emacs previously used tabs+spaces for Scheme,
which is genuinely horrific. >o<
2024-05-13 21:37:29 -05:00
Jaidyn Ann 1b5e9d5b1f Add README.md 2023-01-02 10:12:54 -06:00
Jaidyn Ann fbea00fb66 Rename , now returns attr value 2023-01-02 09:38:48 -06:00
Jaidyn Ann c799d7a37c Tweak the egg/depedencies 2023-01-02 08:43:45 -06:00
3 changed files with 220 additions and 144 deletions

73
README.md Normal file
View File

@ -0,0 +1,73 @@
# xattr
## Interface for extended filesystem attributes
[Extended attributes](https://en.wikipedia.org/wiki/Extended_attributes) are a special type of file attribute that lets you associate key-value pairs with a file without actually modifying the file's contents. Commonly used for program settings, tagging, and file metadata (i.e., storing a file's author with a key like “author.name”) xattrs can be used for used for pretty much anything you can dream up. They're mega-cool and useful.
This is a simple interface for accessing them within [Chicken Scheme](https://call-cc.org/), a friendly Scheme.
## API
### get-xattr
`get-xattr path attr`
Returns the string-value of a file's extended attribute.
If the file has no such attribute, #f is returned. In other cases (e.g., the file doesn't exist, permission not available, no filesystem support), exceptions are raised.
### set-xattr
`set-xattr path attr value`
Sets the string-value of a file's extended attribute.
The set value is returned, if successful.
### remove-xattr
`remove-xattr path attr`
Removes an extended attribute from a file.
The value of the removed attribute is returned, if successful.
### list-xattrs
`list-xattrs path`
Returns a list of the file's extended attribute names.
## Examples
```
(import xattr (chicken io) (chicken file))
;; Create the test file
(call-with-output-file "testing.txt"
(lambda (out-port) (write-line "Just a test!" out-port)))
(set-xattr "testing.txt" "user.dublincore.author" "Migdal E. F. Lemmings")
(set-xattr "testing.txt" "user.dublincore.title" "A Treatise on Almonds")
(get-xattr "testing.txt" "user.dublincore.author")
;; Returns "Migdal E. F. Lemmings"
(list-xattrs "testing.txt")
;; Returns '("user.dublincore.author" "user.dublincore.title")
(remove-xattr "testing.txt" "user.dublincore.title")
;; Returns "A Treatise on Almonds"
(list-xattrs "testing.txt")
;; Now returns '("user.dublincore.author")
```
## On GNU/Linux
Currently, this egg only supports LiGNUx, a platform that has a particularity: The name of each attribute must contain a “namespace,” separated from the actual name by a period. For example, with the attribute named `user.xdg.origin.url`, the namespace is `user` and the name is `xdg.origin.url`. In most cases, you won't stray from the `user` namespace.
Don't forget to prepend “user.” to your attribute names!
A handy list of somewhat-standard extended attributes is hosted on FreeDesktop's website, [here](https://www.freedesktop.org/wiki/CommonExtendedAttributes/).
## Requirements
This egg requires `srfi-1` and `srfi-12`.
## Meta
**Author:** Jaidyn Ann <jadedctrl@posteo.at>
**License:** GPLv3
**Source:** https://github.com/jadedctrl/xattr, https://notabug.org/jadedctrl/xattr

View File

@ -1,9 +1,7 @@
;; -*- Scheme -*- ;; -*- Scheme -*-
((synopsis "Access to extended attributes") ((synopsis "Access to extended attributes")
(author "Jaidyn Ann") (author "Jaidyn Ann")
(category net) (category io)
(license "GPLv3") (license "GPLv3")
(dependencies srfi-1)
(components (extension xattr))) (components (extension xattr)))
;; (csc-options "-O3" "-d1" "-X" "bind" "-s" "-k" "-emit-link-file" "xattr.link" "-o" "xattr_ext.o"))
;; (c-object xattr_ext)))

285
xattr.scm
View File

@ -1,5 +1,5 @@
;; ;;
;; Copyright 2022, Jaidyn Levesque <jadedctrl@posteo.at> ;; Copyright 2024, Jaidyn Levesque <jadedctrl@posteo.at>
;; ;;
;; This program is free software: you can redistribute it and/or ;; This program is free software: you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as ;; modify it under the terms of the GNU General Public License as
@ -16,176 +16,181 @@
;; ;;
(module xattr (module xattr
(get-xattr set-xattr remove-xattr list-xattr) (get-xattr set-xattr remove-xattr list-xattrs)
(import (chicken base) (chicken memory) srfi-1 scheme (chicken foreign) srfi-12) (import scheme
(chicken base) (chicken condition) (chicken foreign) (chicken memory)
(foreign-declare "#include \"xattr_ext.c\"") srfi-1)
;; The direct foreign binding for `get_xattr` (foreign-declare "#include \"xattr_ext.c\"")
(define get-xattr-foreign
(foreign-lambda c-string* "get_xattr" c-string c-string (c-pointer int)))
;; Wrapper around get-xattr-foreign, which throws exceptions and such. ;; The direct foreign binding for `get_xattr`
(define (get-xattr path attr) (define get-xattr-foreign
(let-location ([error-code int]) (foreign-lambda c-string* "get_xattr" c-string c-string (c-pointer int)))
(let ([attr-value (get-xattr-foreign path attr (location error-code))]
[exception (or (getxattr-exception error-code)
(stat-exception error-code))])
(if exception
(signal exception)
attr-value))))
;; The direct foreign binding for `list_xattr` ;; Wrapper around get-xattr-foreign, which throws exceptions and such.
(define list-xattr-foreign (define (get-xattr path attr)
(foreign-lambda (c-pointer char) "list_xattr" c-string (c-pointer ssize_t) (c-pointer int))) (let-location ([error-code int])
(let ([attr-value (get-xattr-foreign path attr (location error-code))]
[exception (or (getxattr-exception error-code)
(stat-exception error-code))])
(if exception
(signal exception)
attr-value))))
;; Wrapper around list-xattr-foreign, which throws exceptions and such. ;; The direct foreign binding for `list_xattr`
(define (list-xattr path) (define list-xattrs-foreign
(let-location ([error-code int] (foreign-lambda (c-pointer char) "list_xattr" c-string (c-pointer ssize_t) (c-pointer int)))
[length ssize_t])
(let ([list-pointer (list-xattr-foreign path (location length) (location error-code))]
[exception (or (getxattr-exception error-code)
(stat-exception error-code))])
(if exception
(signal exception)
;; listxattr offers a const char* \0-delimited list of strings
(pointer->delimited-string-list list-pointer length 0)))))
;; The direct foreign binding for `set_xattr` ;; Wrapper around list-xattrs-foreign, which throws exceptions and such.
(define set-xattr-foreign (define (list-xattrs path)
(foreign-lambda int "set_xattr" c-string c-string c-string (c-pointer int))) (let-location ([error-code int]
[length ssize_t])
(let ([list-pointer (list-xattrs-foreign path (location length) (location error-code))]
[exception (or (getxattr-exception error-code)
(stat-exception error-code))])
(if exception
(signal exception)
;; listxattr offers a const char* \0-delimited list of strings
(pointer->delimited-string-list list-pointer length 0)))))
;; Wrapper around set-xattr-foreign, throwing exceptions and all that jazz ;; The direct foreign binding for `set_xattr`
(define (set-xattr path attr value) (define set-xattr-foreign
(let-location ([error-code int]) (foreign-lambda int "set_xattr" c-string c-string c-string (c-pointer int)))
(let ([return-code (set-xattr-foreign path attr value (location error-code))]
[exception (or (setxattr-exception error-code)
(getxattr-exception error-code)
(stat-exception error-code))])
(if exception
(signal exception)
value))))
;; The direct foreign binding for `remove_xattr` ;; Wrapper around set-xattr-foreign, throwing exceptions and all that jazz
(define remove-xattr-foreign (define (set-xattr path attr value)
(foreign-lambda int "remove_xattr" c-string c-string)) (let-location ([error-code int])
(let ([return-code (set-xattr-foreign path attr value (location error-code))]
[exception (or (setxattr-exception error-code)
(getxattr-exception error-code)
(stat-exception error-code))])
(if exception
(signal exception)
value))))
;; Wrapper around remove-xattr-foreign, blah blah ;; The direct foreign binding for `remove_xattr`
(define (remove-xattr path attr) (define remove-xattr-foreign
(let* ([error-code (remove-xattr-foreign path attr)] (foreign-lambda int "remove_xattr" c-string c-string))
[exception (or (getxattr-exception error-code)
(stat-exception error-code))])
(if exception
(signal exception)
attr)))
;; TODO: These exception functions should be constructed with a macro and a simple ;; Wrapper around remove-xattr-foreign, blah blah
;; list, like `((c-constant symbol error-message) ("E2BIG" 'e2big "The attribute value was too big.")) (define (remove-xattr path attr)
;; Unfortunately, it looks like chicken's macros work a good bit differently from CLs? (let* ([value (get-xattr path attr)]
;; orr I'm losing my mind [error-code (remove-xattr-foreign path attr)]
[exception (or (getxattr-exception error-code)
(stat-exception error-code))])
(if exception
(signal exception)
value)))
;; Return the exception associated with an error-code as per getxattr(2), if it exists ;; TODO: These exception functions should be constructed with a macro and a simple
(define (getxattr-exception error-code) ;; list, like `((c-constant symbol error-message) ("E2BIG" 'e2big "The attribute value was too big."))
(cond
[(eq? error-code (foreign-value "E2BIG" int))
(build-exception 'e2big "The attribute value was too big.")]
[(eq? error-code (foreign-value "ENOTSUP" int))
(build-exception 'enotsup "Extended attributes are disabled or unavailable on this filesystem.")]
[(eq? error-code (foreign-value "ERANGE" int))
(build-exception 'erange "The xattr module's buffer was too small for the attribute value. Whoops <w<\"")]
[#t
#f]))
;; Return the exception associated with a setxattr(2) error-code, if it exists. ;; Return the exception associated with an error-code as per getxattr(2), if it exists
(define (setxattr-exception error-code) (define (getxattr-exception error-code)
(cond (cond
[(eq? error-code (foreign-value "EDQUOT" int)) [(eq? error-code (foreign-value "E2BIG" int))
(build-exception 'edquot "Setting this attribute would violate disk quota.")] (build-exception 'e2big "The attribute value was too big.")]
[(eq? error-code (foreign-value "ENOSPC" int)) [(eq? error-code (foreign-value "ENOTSUP" int))
(build-exception 'enospc "There is insufficient space to store the extended attribute.")] (build-exception 'enotsup "Extended attributes are disabled or unavailable on this filesystem.")]
[(eq? error-code (foreign-value "EPERM" int)) [(eq? error-code (foreign-value "ERANGE" int))
(build-exception 'eperm "The file is immutable or append-only.")] (build-exception 'erange "The xattr module's buffer was too small for the attribute value. Whoops <w<\"")]
[(eq? error-code (foreign-value "ERANGE" int)) [#t
(build-exception 'erange "Either the attribute name or value exceeds the filesystem's limit.")] #f]))
[#t
#f]))
;; Return the exception associated with an error-code as per stat(2), if applicable ;; Return the exception associated with a setxattr(2) error-code, if it exists.
(define (stat-exception error-code) (define (setxattr-exception error-code)
(cond (cond
[(eq? error-code (foreign-value "EACCES" int)) [(eq? error-code (foreign-value "EDQUOT" int))
(build-exception 'file "Search permission denied for a parent directory.")] (build-exception 'edquot "Setting this attribute would violate disk quota.")]
[(eq? error-code (foreign-value "EBADF" int)) [(eq? error-code (foreign-value "ENOSPC" int))
(build-exception 'file "The file-descriptor is bad. Wait, wha…?")] (build-exception 'enospc "There is insufficient space to store the extended attribute.")]
[(eq? error-code (foreign-value "EFAULT" int)) [(eq? error-code (foreign-value "EPERM" int))
(build-exception 'file "The address is bad. OK.")] (build-exception 'eperm "The file is immutable or append-only.")]
[(eq? error-code (foreign-value "EINVAL" int)) [(eq? error-code (foreign-value "ERANGE" int))
(build-exception 'file "Invalid fstatat flag.")] (build-exception 'erange "Either the attribute name or value exceeds the filesystem's limit.")]
[(eq? error-code (foreign-value "ELOOP" int)) [#t
(build-exception 'file "Too many symbolic links in recursion.")] #f]))
[(eq? error-code (foreign-value "ENAMETOOLONG" int))
(build-exception 'file "The given pathname is too long.")]
[(eq? error-code (foreign-value "ENOENT" int))
(build-exception 'file "This file doesn't exist, or there is a dangling symlink.")]
[(eq? error-code (foreign-value "ENOMEM" int))
(build-exception 'file "Out of memory.")]
[(eq? error-code (foreign-value "ENOTDIR" int))
(build-exception 'file "Component of path isn't a proper directory.")]
[(eq? error-code (foreign-value "EOVERFLOW" int))
(build-exception 'file "An overflow has occured.")]
[#t
#f]))
;; Creates a generic exception with given symbol and message ;; Return the exception associated with an error-code as per stat(2), if applicable
(define (build-exception symbol message) (define (stat-exception error-code)
(signal (make-property-condition 'exn 'location symbol 'message message))) (cond
[(eq? error-code (foreign-value "EACCES" int))
(build-exception 'file "Search permission denied for a parent directory.")]
[(eq? error-code (foreign-value "EBADF" int))
(build-exception 'file "The file-descriptor is bad. Wait, wha…?")]
[(eq? error-code (foreign-value "EFAULT" int))
(build-exception 'file "The address is bad. OK.")]
[(eq? error-code (foreign-value "EINVAL" int))
(build-exception 'file "Invalid fstatat flag.")]
[(eq? error-code (foreign-value "ELOOP" int))
(build-exception 'file "Too many symbolic links in recursion.")]
[(eq? error-code (foreign-value "ENAMETOOLONG" int))
(build-exception 'file "The given pathname is too long.")]
[(eq? error-code (foreign-value "ENOENT" int))
(build-exception 'file "This file doesn't exist, or there is a dangling symlink.")]
[(eq? error-code (foreign-value "ENOMEM" int))
(build-exception 'file "Out of memory.")]
[(eq? error-code (foreign-value "ENOTDIR" int))
(build-exception 'file "Component of path isn't a proper directory.")]
[(eq? error-code (foreign-value "EOVERFLOW" int))
(build-exception 'file "An overflow has occured.")]
[#t
#f]))
;; Some C functions offer up a string-list that isn't a two-dimensional array, but a ;; Creates a generic exception with given symbol and message
;; one-dimensional array with given length and strings separated by a given delimeter. (define (build-exception symbol message)
;; This takes that sort of pointer and gives you a nice list of strings. (signal (make-property-condition 'exn 'location symbol 'message message)))
(define (pointer->delimited-string-list pointer length delimiter)
(let* ([is-zero (lambda (num) (eq? num 0))]
[map-to-char (lambda (a) (map integer->char a))]
[byte-list (drop-right (pointer->integers pointer length) 1)])
(map list->string
(map map-to-char
(split-list is-zero byte-list)))))
;; Takes a pointer and returns a list of bytes of a given length ;; Some C functions offer up a string-list that isn't a two-dimensional array, but a
(define (pointer->integers pointer length) ;; one-dimensional array with given length and strings separated by a given delimeter.
(let ([byte (pointer-s8-ref pointer)]) ;; This takes that sort of pointer and gives you a nice list of strings.
(if (eq? length 0) (define (pointer->delimited-string-list pointer length delimiter)
byte (let* ([is-zero (lambda (num) (eq? num 0))]
(append (list byte) [map-to-char (lambda (a) (map integer->char a))]
(pointer->integers (pointer+ pointer 1) (- length 1)))))) [pointer-ints (pointer->integers pointer length)]
[byte-list (when (list? pointer-ints)
(drop-right pointer-ints 1))])
(if (and (list? byte-list)
(not (null-list? byte-list)))
(map list->string
(map map-to-char
(split-list is-zero byte-list)))
'())))
;; Split a list into sublists, with items passing `test` being the delimiters ;; Takes a pointer and returns a list of bytes of a given length
(define (split-list test list) (define (pointer->integers pointer length)
(let ([before (take-while (compose not test) list)] (let ([byte (pointer-s8-ref pointer)])
[after (drop-while (compose not test) list)]) (if (eq? length 0)
(cond byte
[(or (null-list? after) (null-list? (cdr after))) (append (list byte)
`(,before)] (pointer->integers (pointer+ pointer 1) (- length 1))))))
[(null-list? before)
(split-list test (cdr after))]
[#t
(append `(,before) (split-list test (cdr after)))])))
)
;; Split a list into sublists, with items passing `test` being the delimiters
(define (split-list test list)
(let ([before (take-while (compose not test) list)]
[after (drop-while (compose not test) list)])
(cond
[(or (null-list? after) (null-list? (cdr after)))
`(,before)]
[(null-list? before)
(split-list test (cdr after))]
[#t
(append `(,before) (split-list test (cdr after)))]))))