xattr.scm 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. ;;
  2. ;; Copyright 2022, Jaidyn Levesque <jadedctrl@posteo.at>
  3. ;;
  4. ;; This program is free software: you can redistribute it and/or
  5. ;; modify it under the terms of the GNU General Public License as
  6. ;; published by the Free Software Foundation, either version 3 of
  7. ;; the License, or (at your option) any later version.
  8. ;;
  9. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. ;;
  17. (module xattr
  18. (get-xattr set-xattr remove-xattr list-xattr)
  19. (import (chicken base) (chicken memory) srfi-1 scheme (chicken foreign) srfi-12)
  20. (foreign-declare "#include \"xattr_ext.c\"")
  21. ;; The direct foreign binding for `get_xattr`
  22. (define get-xattr-foreign
  23. (foreign-lambda c-string* "get_xattr" c-string c-string (c-pointer int)))
  24. ;; Wrapper around get-xattr-foreign, which throws exceptions and such.
  25. (define (get-xattr path attr)
  26. (let-location ([error-code int])
  27. (let ([attr-value (get-xattr-foreign path attr (location error-code))]
  28. [exception (or (getxattr-exception error-code)
  29. (stat-exception error-code))])
  30. (if exception
  31. (signal exception)
  32. attr-value))))
  33. ;; The direct foreign binding for `list_xattr`
  34. (define list-xattr-foreign
  35. (foreign-lambda (c-pointer char) "list_xattr" c-string (c-pointer ssize_t) (c-pointer int)))
  36. ;; Wrapper around list-xattr-foreign, which throws exceptions and such.
  37. (define (list-xattr path)
  38. (let-location ([error-code int]
  39. [length ssize_t])
  40. (let ([list-pointer (list-xattr-foreign path (location length) (location error-code))]
  41. [exception (or (getxattr-exception error-code)
  42. (stat-exception error-code))])
  43. (if exception
  44. (signal exception)
  45. ;; listxattr offers a const char* \0-delimited list of strings
  46. (pointer->delimited-string-list list-pointer length 0)))))
  47. ;; The direct foreign binding for `set_xattr`
  48. (define set-xattr-foreign
  49. (foreign-lambda int "set_xattr" c-string c-string c-string (c-pointer int)))
  50. ;; Wrapper around set-xattr-foreign, throwing exceptions and all that jazz
  51. (define (set-xattr path attr value)
  52. (let-location ([error-code int])
  53. (let ([return-code (set-xattr-foreign path attr value (location error-code))]
  54. [exception (or (setxattr-exception error-code)
  55. (getxattr-exception error-code)
  56. (stat-exception error-code))])
  57. (if exception
  58. (signal exception)
  59. value))))
  60. ;; The direct foreign binding for `remove_xattr`
  61. (define remove-xattr-foreign
  62. (foreign-lambda int "remove_xattr" c-string c-string))
  63. ;; Wrapper around remove-xattr-foreign, blah blah
  64. (define (remove-xattr path attr)
  65. (let* ([error-code (remove-xattr-foreign path attr)]
  66. [exception (or (getxattr-exception error-code)
  67. (stat-exception error-code))])
  68. (if exception
  69. (signal exception)
  70. attr)))
  71. ;; TODO: These exception functions should be constructed with a macro and a simple
  72. ;; list, like `((c-constant symbol error-message) ("E2BIG" 'e2big "The attribute value was too big."))
  73. ;; Unfortunately, it looks like chicken's macros work a good bit differently from CLs?
  74. ;; orr I'm losing my mind
  75. ;; Return the exception associated with an error-code as per getxattr(2), if it exists
  76. (define (getxattr-exception error-code)
  77. (cond
  78. [(eq? error-code (foreign-value "E2BIG" int))
  79. (build-exception 'e2big "The attribute value was too big.")]
  80. [(eq? error-code (foreign-value "ENOTSUP" int))
  81. (build-exception 'enotsup "Extended attributes are disabled or unavailable on this filesystem.")]
  82. [(eq? error-code (foreign-value "ERANGE" int))
  83. (build-exception 'erange "The xattr module's buffer was too small for the attribute value. Whoops <w<\"")]
  84. [#t
  85. #f]))
  86. ;; Return the exception associated with a setxattr(2) error-code, if it exists.
  87. (define (setxattr-exception error-code)
  88. (cond
  89. [(eq? error-code (foreign-value "EDQUOT" int))
  90. (build-exception 'edquot "Setting this attribute would violate disk quota.")]
  91. [(eq? error-code (foreign-value "ENOSPC" int))
  92. (build-exception 'enospc "There is insufficient space to store the extended attribute.")]
  93. [(eq? error-code (foreign-value "EPERM" int))
  94. (build-exception 'eperm "The file is immutable or append-only.")]
  95. [(eq? error-code (foreign-value "ERANGE" int))
  96. (build-exception 'erange "Either the attribute name or value exceeds the filesystem's limit.")]
  97. [#t
  98. #f]))
  99. ;; Return the exception associated with an error-code as per stat(2), if applicable
  100. (define (stat-exception error-code)
  101. (cond
  102. [(eq? error-code (foreign-value "EACCES" int))
  103. (build-exception 'file "Search permission denied for a parent directory.")]
  104. [(eq? error-code (foreign-value "EBADF" int))
  105. (build-exception 'file "The file-descriptor is bad. Wait, wha…?")]
  106. [(eq? error-code (foreign-value "EFAULT" int))
  107. (build-exception 'file "The address is bad. OK.")]
  108. [(eq? error-code (foreign-value "EINVAL" int))
  109. (build-exception 'file "Invalid fstatat flag.")]
  110. [(eq? error-code (foreign-value "ELOOP" int))
  111. (build-exception 'file "Too many symbolic links in recursion.")]
  112. [(eq? error-code (foreign-value "ENAMETOOLONG" int))
  113. (build-exception 'file "The given pathname is too long.")]
  114. [(eq? error-code (foreign-value "ENOENT" int))
  115. (build-exception 'file "This file doesn't exist, or there is a dangling symlink.")]
  116. [(eq? error-code (foreign-value "ENOMEM" int))
  117. (build-exception 'file "Out of memory.")]
  118. [(eq? error-code (foreign-value "ENOTDIR" int))
  119. (build-exception 'file "Component of path isn't a proper directory.")]
  120. [(eq? error-code (foreign-value "EOVERFLOW" int))
  121. (build-exception 'file "An overflow has occured.")]
  122. [#t
  123. #f]))
  124. ;; Creates a generic exception with given symbol and message
  125. (define (build-exception symbol message)
  126. (signal (make-property-condition 'exn 'location symbol 'message message)))
  127. ;; Some C functions offer up a string-list that isn't a two-dimensional array, but a
  128. ;; one-dimensional array with given length and strings separated by a given delimeter.
  129. ;; This takes that sort of pointer and gives you a nice list of strings.
  130. (define (pointer->delimited-string-list pointer length delimiter)
  131. (let* ([is-zero (lambda (num) (eq? num 0))]
  132. [map-to-char (lambda (a) (map integer->char a))]
  133. [byte-list (drop-right (pointer->integers pointer length) 1)])
  134. (map list->string
  135. (map map-to-char
  136. (split-list is-zero byte-list)))))
  137. ;; Takes a pointer and returns a list of bytes of a given length
  138. (define (pointer->integers pointer length)
  139. (let ([byte (pointer-s8-ref pointer)])
  140. (if (eq? length 0)
  141. byte
  142. (append (list byte)
  143. (pointer->integers (pointer+ pointer 1) (- length 1))))))
  144. ;; Split a list into sublists, with items passing `test` being the delimiters
  145. (define (split-list test list)
  146. (let ([before (take-while (compose not test) list)]
  147. [after (drop-while (compose not test) list)])
  148. (cond
  149. [(or (null-list? after) (null-list? (cdr after)))
  150. `(,before)]
  151. [(null-list? before)
  152. (split-list test (cdr after))]
  153. [#t
  154. (append `(,before) (split-list test (cdr after)))])))
  155. )