Raymond Toy pushed to branch master at cmucl / cmucl
Commits:
-
bc8cb405
by Raymond Toy at 2023-12-07T23:09:31+00:00
-
81f0d53c
by Raymond Toy at 2023-12-07T23:09:36+00:00
7 changed files:
- src/code/exports.lisp
- src/code/filesys.lisp
- src/code/os.lisp
- src/general-info/release-21f.md
- src/i18n/locale/cmucl-os.pot
- tests/os.lisp
- tests/pathname.lisp
Changes:
| ... | ... | @@ -2074,7 +2074,7 @@ |
| 2074 | 2074 | "FD-STREAM-P" "FIND-IF-IN-CLOSURE" "FOREIGN-SYMBOL-ADDRESS"
|
| 2075 | 2075 | "FOREIGN-SYMBOL-CODE-ADDRESS" "FOREIGN-SYMBOL-DATA-ADDRESS"
|
| 2076 | 2076 | "GET-PAGE-SIZE" "GET-SYSTEM-INFO"
|
| 2077 | - "GET-USER-HOMEDIR-PATHNAME"
|
|
| 2077 | + "GET-USER-HOMEDIR-NAMESTRING"
|
|
| 2078 | 2078 | "IGNORE-INTERRUPT"
|
| 2079 | 2079 | "INT-SAP" "INVALIDATE-DESCRIPTOR" "IO-TIMEOUT"
|
| 2080 | 2080 | "LISP-STREAM" "LONG-FLOAT-RADIX" "LONG-WORDS"
|
| ... | ... | @@ -301,105 +301,136 @@ |
| 301 | 301 | (return (values (remove-backslashes namestr start index)
|
| 302 | 302 | (1+ index)))))))))
|
| 303 | 303 | |
| 304 | +(defun expand-tilde-user-name (str start end)
|
|
| 305 | + ;; Quick exit if STR doesn't start with ~ or we have an empty string.
|
|
| 306 | + (when (or (= start end)
|
|
| 307 | + (char/= (schar str start) #\~))
|
|
| 308 | + (return-from expand-tilde-user-name
|
|
| 309 | + (values str start end)))
|
|
| 310 | +
|
|
| 311 | + (let ((end-user (position #\/ str :start start :end end)))
|
|
| 312 | + ;; Quick exit if we can't find a "/" to terminate the user name.
|
|
| 313 | + (unless end-user
|
|
| 314 | + (return-from expand-tilde-user-name
|
|
| 315 | + (values str start end)))
|
|
| 316 | + (let* ((user-name (subseq str (1+ start) end-user))
|
|
| 317 | + (homedir (get-user-homedir-namestring user-name)))
|
|
| 318 | + (unless homedir
|
|
| 319 | + (error "Unknown user ~S in namestring ~S" user-name (subseq str start end)))
|
|
| 320 | + ;; Replace the ~user part with the home directory, adjusting END
|
|
| 321 | + ;; because of the replacement.
|
|
| 322 | + (values (concatenate 'simple-base-string
|
|
| 323 | + (subseq str 0 start)
|
|
| 324 | + homedir
|
|
| 325 | + (subseq str end-user))
|
|
| 326 | + start
|
|
| 327 | + (+ end (- (length homedir)
|
|
| 328 | + (length user-name)
|
|
| 329 | + 1))))))
|
|
| 330 | +
|
|
| 304 | 331 | (defun parse-unix-namestring (namestr start end)
|
| 305 | 332 | (declare (type simple-base-string namestr)
|
| 306 | 333 | (type index start end))
|
| 307 | - (multiple-value-bind
|
|
| 308 | - (absolute pieces)
|
|
| 309 | - (split-at-slashes namestr start end)
|
|
| 310 | - (let ((search-list
|
|
| 311 | - (if absolute
|
|
| 312 | - nil
|
|
| 313 | - (let ((first (car pieces)))
|
|
| 314 | - (multiple-value-bind
|
|
| 315 | - (search-list new-start)
|
|
| 316 | - (maybe-extract-search-list namestr
|
|
| 317 | - (car first) (cdr first))
|
|
| 318 | - (when search-list
|
|
| 319 | - ;; Lose if this search-list is already defined as
|
|
| 320 | - ;; a logical host. Since the syntax for
|
|
| 321 | - ;; search-lists and logical pathnames are the
|
|
| 322 | - ;; same, we can't allow the creation of one when
|
|
| 323 | - ;; the other is defined.
|
|
| 324 | - (when (find-logical-host search-list nil)
|
|
| 325 | - (error (intl:gettext "~A already names a logical host") search-list))
|
|
| 326 | - (setf absolute t)
|
|
| 327 | - (setf (car first) new-start))
|
|
| 328 | - search-list)))))
|
|
| 329 | - (multiple-value-bind (name type version)
|
|
| 330 | - (let* ((tail (car (last pieces)))
|
|
| 331 | - (tail-start (car tail))
|
|
| 332 | - (tail-end (cdr tail)))
|
|
| 333 | - (unless (= tail-start tail-end)
|
|
| 334 | - (setf pieces (butlast pieces))
|
|
| 335 | - (cond ((string= namestr ".." :start1 tail-start :end1 tail-end)
|
|
| 336 | - ;; ".." is a directory. Add this piece to the
|
|
| 337 | - ;; list of pieces, and make the name/type/version
|
|
| 338 | - ;; nil.
|
|
| 339 | - (setf pieces (append pieces (list (cons tail-start tail-end))))
|
|
| 340 | - (values nil nil nil))
|
|
| 341 | - ((string= namestr "." :start1 tail-start :end1 tail-end)
|
|
| 342 | - ;; "." is a directory as well.
|
|
| 343 | - (setf pieces (append pieces (list (cons tail-start tail-end))))
|
|
| 344 | - (values nil nil nil))
|
|
| 345 | - ((not (find-if-not #'(lambda (c)
|
|
| 346 | - (char= c #\.))
|
|
| 347 | - namestr :start tail-start :end tail-end))
|
|
| 348 | - ;; Got a bunch of dots. Make it a file of the
|
|
| 349 | - ;; same name, and type the empty string.
|
|
| 350 | - (values (subseq namestr tail-start (1- tail-end)) "" nil))
|
|
| 351 | - (t
|
|
| 352 | - (extract-name-type-and-version namestr tail-start tail-end)))))
|
|
| 353 | - ;; PVE: Make sure there are no illegal characters in the name
|
|
| 354 | - ;; such as #\Null and #\/.
|
|
| 355 | - (when (and (stringp name)
|
|
| 356 | - (find-if #'(lambda (x)
|
|
| 357 | - (or (char= x #\Null) (char= x #\/)))
|
|
| 358 | - name))
|
|
| 359 | - (error 'parse-error))
|
|
| 360 | - ;; Now we have everything we want. So return it.
|
|
| 361 | - (values nil ; no host for unix namestrings.
|
|
| 362 | - nil ; no devices for unix namestrings.
|
|
| 363 | - (collect ((dirs))
|
|
| 364 | - (when search-list
|
|
| 365 | - (dirs (intern-search-list search-list)))
|
|
| 366 | - (dolist (piece pieces)
|
|
| 367 | - (let ((piece-start (car piece))
|
|
| 368 | - (piece-end (cdr piece)))
|
|
| 369 | - (unless (= piece-start piece-end)
|
|
| 370 | - (cond ((string= namestr ".." :start1 piece-start
|
|
| 371 | - :end1 piece-end)
|
|
| 372 | - (dirs :up))
|
|
| 373 | - ((string= namestr "**" :start1 piece-start
|
|
| 374 | - :end1 piece-end)
|
|
| 375 | - (dirs :wild-inferiors))
|
|
| 376 | - (t
|
|
| 377 | - (dirs (maybe-make-pattern namestr
|
|
| 378 | - piece-start
|
|
| 379 | - piece-end)))))))
|
|
| 380 | - (cond (absolute
|
|
| 381 | - (cons :absolute (dirs)))
|
|
| 382 | - ((dirs)
|
|
| 383 | - ;; "." in a :relative directory is the same
|
|
| 384 | - ;; as if it weren't there, so remove them.
|
|
| 385 | - (cons :relative (delete "." (dirs) :test #'equal)))
|
|
| 386 | - (t
|
|
| 387 | - ;; If there is no directory and the name is
|
|
| 388 | - ;; "." and the type is NIL, we really got
|
|
| 389 | - ;; directory ".", so make it so.
|
|
| 390 | - (if (and (equal name ".")
|
|
| 391 | - (null type))
|
|
| 392 | - (list :relative)
|
|
| 393 | - nil))))
|
|
| 394 | - ;; A file with name "." and type NIL can't be the name
|
|
| 395 | - ;; of file on Unix because it's a directory. This was
|
|
| 396 | - ;; handled above, so we can just set the name to nil.
|
|
| 397 | - (if (and (equal name ".")
|
|
| 398 | - (null type))
|
|
| 399 | - nil
|
|
| 400 | - name)
|
|
| 401 | - type
|
|
| 402 | - version)))))
|
|
| 334 | + ;; Look for "~user/" (or "~/"). If found replace it with the user's
|
|
| 335 | + ;; home directory
|
|
| 336 | + (multiple-value-bind (namestr start end)
|
|
| 337 | + (expand-tilde-user-name namestr start end)
|
|
| 338 | + (multiple-value-bind
|
|
| 339 | + (absolute pieces)
|
|
| 340 | + (split-at-slashes namestr start end)
|
|
| 341 | + (let ((search-list
|
|
| 342 | + (if absolute
|
|
| 343 | + nil
|
|
| 344 | + (let ((first (car pieces)))
|
|
| 345 | + (multiple-value-bind
|
|
| 346 | + (search-list new-start)
|
|
| 347 | + (maybe-extract-search-list namestr
|
|
| 348 | + (car first) (cdr first))
|
|
| 349 | + (when search-list
|
|
| 350 | + ;; Lose if this search-list is already defined as
|
|
| 351 | + ;; a logical host. Since the syntax for
|
|
| 352 | + ;; search-lists and logical pathnames are the
|
|
| 353 | + ;; same, we can't allow the creation of one when
|
|
| 354 | + ;; the other is defined.
|
|
| 355 | + (when (find-logical-host search-list nil)
|
|
| 356 | + (error (intl:gettext "~A already names a logical host") search-list))
|
|
| 357 | + (setf absolute t)
|
|
| 358 | + (setf (car first) new-start))
|
|
| 359 | + search-list)))))
|
|
| 360 | + (multiple-value-bind (name type version)
|
|
| 361 | + (let* ((tail (car (last pieces)))
|
|
| 362 | + (tail-start (car tail))
|
|
| 363 | + (tail-end (cdr tail)))
|
|
| 364 | + (unless (= tail-start tail-end)
|
|
| 365 | + (setf pieces (butlast pieces))
|
|
| 366 | + (cond ((string= namestr ".." :start1 tail-start :end1 tail-end)
|
|
| 367 | + ;; ".." is a directory. Add this piece to the
|
|
| 368 | + ;; list of pieces, and make the name/type/version
|
|
| 369 | + ;; nil.
|
|
| 370 | + (setf pieces (append pieces (list (cons tail-start tail-end))))
|
|
| 371 | + (values nil nil nil))
|
|
| 372 | + ((string= namestr "." :start1 tail-start :end1 tail-end)
|
|
| 373 | + ;; "." is a directory as well.
|
|
| 374 | + (setf pieces (append pieces (list (cons tail-start tail-end))))
|
|
| 375 | + (values nil nil nil))
|
|
| 376 | + ((not (find-if-not #'(lambda (c)
|
|
| 377 | + (char= c #\.))
|
|
| 378 | + namestr :start tail-start :end tail-end))
|
|
| 379 | + ;; Got a bunch of dots. Make it a file of the
|
|
| 380 | + ;; same name, and type the empty string.
|
|
| 381 | + (values (subseq namestr tail-start (1- tail-end)) "" nil))
|
|
| 382 | + (t
|
|
| 383 | + (extract-name-type-and-version namestr tail-start tail-end)))))
|
|
| 384 | + ;; PVE: Make sure there are no illegal characters in the name
|
|
| 385 | + ;; such as #\Null and #\/.
|
|
| 386 | + (when (and (stringp name)
|
|
| 387 | + (find-if #'(lambda (x)
|
|
| 388 | + (or (char= x #\Null) (char= x #\/)))
|
|
| 389 | + name))
|
|
| 390 | + (error 'parse-error))
|
|
| 391 | + ;; Now we have everything we want. So return it.
|
|
| 392 | + (values nil ; no host for unix namestrings.
|
|
| 393 | + nil ; no devices for unix namestrings.
|
|
| 394 | + (collect ((dirs))
|
|
| 395 | + (when search-list
|
|
| 396 | + (dirs (intern-search-list search-list)))
|
|
| 397 | + (dolist (piece pieces)
|
|
| 398 | + (let ((piece-start (car piece))
|
|
| 399 | + (piece-end (cdr piece)))
|
|
| 400 | + (unless (= piece-start piece-end)
|
|
| 401 | + (cond ((string= namestr ".." :start1 piece-start
|
|
| 402 | + :end1 piece-end)
|
|
| 403 | + (dirs :up))
|
|
| 404 | + ((string= namestr "**" :start1 piece-start
|
|
| 405 | + :end1 piece-end)
|
|
| 406 | + (dirs :wild-inferiors))
|
|
| 407 | + (t
|
|
| 408 | + (dirs (maybe-make-pattern namestr
|
|
| 409 | + piece-start
|
|
| 410 | + piece-end)))))))
|
|
| 411 | + (cond (absolute
|
|
| 412 | + (cons :absolute (dirs)))
|
|
| 413 | + ((dirs)
|
|
| 414 | + ;; "." in a :relative directory is the same
|
|
| 415 | + ;; as if it weren't there, so remove them.
|
|
| 416 | + (cons :relative (delete "." (dirs) :test #'equal)))
|
|
| 417 | + (t
|
|
| 418 | + ;; If there is no directory and the name is
|
|
| 419 | + ;; "." and the type is NIL, we really got
|
|
| 420 | + ;; directory ".", so make it so.
|
|
| 421 | + (if (and (equal name ".")
|
|
| 422 | + (null type))
|
|
| 423 | + (list :relative)
|
|
| 424 | + nil))))
|
|
| 425 | + ;; A file with name "." and type NIL can't be the name
|
|
| 426 | + ;; of file on Unix because it's a directory. This was
|
|
| 427 | + ;; handled above, so we can just set the name to nil.
|
|
| 428 | + (if (and (equal name ".")
|
|
| 429 | + (null type))
|
|
| 430 | + nil
|
|
| 431 | + name)
|
|
| 432 | + type
|
|
| 433 | + version))))))
|
|
| 403 | 434 | |
| 404 | 435 | (defun unparse-unix-host (pathname)
|
| 405 | 436 | (declare (type pathname pathname)
|
| ... | ... | @@ -58,32 +58,39 @@ |
| 58 | 58 | (unix:get-unix-error-msg utime)))
|
| 59 | 59 | (values utime stime major-fault))))
|
| 60 | 60 | |
| 61 | -;;; GET-USER-HOMEDIR-PATHNAME -- Public
|
|
| 61 | +;;; GET-USER-HOMEDIR-NAMESTRING -- Public
|
|
| 62 | 62 | ;;;
|
| 63 | -(defun get-user-homedir-pathname (name)
|
|
| 64 | - _N"Get the user home directory for user named NAME. Two values are
|
|
| 65 | - returned: the pathname of the home directory and a status code. If
|
|
| 66 | - the home directory does not exist NIL is returned. The status is 0
|
|
| 67 | - if no errors occurred. Otherwise a non-zero value is returned.
|
|
| 68 | - Examining errno may give information about what failed."
|
|
| 69 | - (alien:with-alien ((status c-call:int))
|
|
| 70 | - (let (result)
|
|
| 71 | - (unwind-protect
|
|
| 72 | - (progn
|
|
| 73 | - (setf result
|
|
| 74 | - (alien:alien-funcall
|
|
| 75 | - (alien:extern-alien "os_get_user_homedir"
|
|
| 76 | - (function (alien:* c-call:c-string)
|
|
| 77 | - c-call:c-string
|
|
| 78 | - (* c-call:int)))
|
|
| 79 | - name
|
|
| 80 | - (alien:addr status)))
|
|
| 81 | - (if (and (zerop status)
|
|
| 82 | - (not (alien:null-alien result)))
|
|
| 83 | - (values (pathname
|
|
| 84 | - (concatenate 'string
|
|
| 85 | - (alien:cast result c-call:c-string)
|
|
| 86 | - "/"))
|
|
| 87 | - status)
|
|
| 88 | - (values nil status)))
|
|
| 89 | - (alien:free-alien result))))) |
|
| 63 | +(defun get-user-homedir-namestring (name)
|
|
| 64 | + _N"Get the user home directory for user named NAME. If NAME is the empty
|
|
| 65 | + string, the home directory of the current user is returned.
|
|
| 66 | + |
|
| 67 | + Two values are returned: the pathname of the home directory and a
|
|
| 68 | + status code. If the home directory does not exist NIL is returned.
|
|
| 69 | + The status is 0 if no errors occurred. Otherwise a non-zero value
|
|
| 70 | + is returned. Examining errno may give information about what failed."
|
|
| 71 | + (cond
|
|
| 72 | + ((zerop (length name))
|
|
| 73 | + (multiple-value-bind (user-info status)
|
|
| 74 | + (unix:unix-getpwuid (unix:unix-getuid))
|
|
| 75 | + (values (when user-info
|
|
| 76 | + (unix:user-info-dir user-info))
|
|
| 77 | + status)))
|
|
| 78 | + (t
|
|
| 79 | + (alien:with-alien ((status c-call:int))
|
|
| 80 | + (let (result)
|
|
| 81 | + (unwind-protect
|
|
| 82 | + (progn
|
|
| 83 | + (setf result
|
|
| 84 | + (alien:alien-funcall
|
|
| 85 | + (alien:extern-alien "os_get_user_homedir"
|
|
| 86 | + (function (alien:* c-call:c-string)
|
|
| 87 | + c-call:c-string
|
|
| 88 | + (* c-call:int)))
|
|
| 89 | + name
|
|
| 90 | + (alien:addr status)))
|
|
| 91 | + (if (and (zerop status)
|
|
| 92 | + (not (alien:null-alien result)))
|
|
| 93 | + (values (alien:cast result c-call:c-string)
|
|
| 94 | + status)
|
|
| 95 | + (values nil status)))
|
|
| 96 | + (alien:free-alien result))))))) |
| ... | ... | @@ -39,6 +39,7 @@ public domain. |
| 39 | 39 | * ~~#253~~ Block-compile list-to-hashtable and callers
|
| 40 | 40 | * ~~#258~~ Remove `get-page-size` from linux-os.lisp
|
| 41 | 41 | * ~~#269~~ Add function to get user's home directory
|
| 42 | + * ~~#266~~ Support "~user" in namestrings
|
|
| 42 | 43 | * Other changes:
|
| 43 | 44 | * Improvements to the PCL implementation of CLOS:
|
| 44 | 45 | * Changes to building procedure:
|
| ... | ... | @@ -35,10 +35,12 @@ msgstr "" |
| 35 | 35 | |
| 36 | 36 | #: src/code/os.lisp
|
| 37 | 37 | msgid ""
|
| 38 | -"Get the user home directory for user named NAME. Two values are\n"
|
|
| 39 | -" returned: the pathname of the home directory and a status code. If\n"
|
|
| 40 | -" the home directory does not exist NIL is returned. The status is 0\n"
|
|
| 41 | -" if no errors occurred. Otherwise a non-zero value is returned.\n"
|
|
| 42 | -" Examining errno may give information about what failed."
|
|
| 38 | +"Get the user home directory for user named NAME. If NAME is the empty\n"
|
|
| 39 | +" string, the home directory of the current user is returned.\n"
|
|
| 40 | +"\n"
|
|
| 41 | +" Two values are returned: the pathname of the home directory and a\n"
|
|
| 42 | +" status code. If the home directory does not exist NIL is returned.\n"
|
|
| 43 | +" The status is 0 if no errors occurred. Otherwise a non-zero value\n"
|
|
| 44 | +" is returned. Examining errno may give information about what failed."
|
|
| 43 | 45 | msgstr ""
|
| 44 | 46 |
| ... | ... | @@ -7,7 +7,7 @@ |
| 7 | 7 | (define-test user-homedir.1
|
| 8 | 8 | "Test user-homedir"
|
| 9 | 9 | (:tag :issues)
|
| 10 | - ;; Simple test to see if get-user-homedir-pathname returns the
|
|
| 10 | + ;; Simple test to see if get-user-homedir-namestring returns the
|
|
| 11 | 11 | ;; expected value. Use getuid and getpwuid to figure out what the
|
| 12 | 12 | ;; name and home directory should be.
|
| 13 | 13 | (let* ((uid (unix:unix-getuid))
|
| ... | ... | @@ -15,15 +15,13 @@ |
| 15 | 15 | (assert-true uid)
|
| 16 | 16 | (assert-true user-info)
|
| 17 | 17 | (let* ((info-dir (unix:user-info-dir user-info))
|
| 18 | - (info-name (unix:user-info-name user-info))
|
|
| 19 | - (expected-home-pathname (pathname
|
|
| 20 | - (concatenate 'string info-dir "/"))))
|
|
| 21 | - (multiple-value-bind (home-pathname status)
|
|
| 22 | - (system:get-user-homedir-pathname info-name)
|
|
| 18 | + (info-name (unix:user-info-name user-info)))
|
|
| 19 | + (multiple-value-bind (home-namestring status)
|
|
| 20 | + (system:get-user-homedir-namestring info-name)
|
|
| 23 | 21 | (assert-true info-dir)
|
| 24 | 22 | (assert-true info-name)
|
| 25 | 23 | |
| 26 | - (assert-equal home-pathname expected-home-pathname)
|
|
| 24 | + (assert-equal home-namestring info-dir)
|
|
| 27 | 25 | (assert-eql status 0)))))
|
| 28 | 26 | |
| 29 | 27 | (define-test user-homedir.2
|
| ... | ... | @@ -33,6 +31,6 @@ |
| 33 | 31 | ;; value for a user that does not exist. Well, we assume such a
|
| 34 | 32 | ;; user doesn't exist.
|
| 35 | 33 | (multiple-value-bind (home-pathname status)
|
| 36 | - (system:get-user-homedir-pathname "zotuserunknown")
|
|
| 34 | + (system:get-user-homedir-namestring "zotuserunknown")
|
|
| 37 | 35 | (assert-eql home-pathname nil)
|
| 38 | 36 | (assert-eql status 0))) |
| ... | ... | @@ -111,3 +111,34 @@ |
| 111 | 111 | test
|
| 112 | 112 | (assert-equal printed-value (output pathname))
|
| 113 | 113 | (assert-equal namestring (namestring pathname))))))
|
| 114 | + |
|
| 115 | +(define-test issue.266.pathname-tilde.unknown-user
|
|
| 116 | + (:tag :issues)
|
|
| 117 | + ;; This assumes that there's no user named "zotunknown".
|
|
| 118 | + (assert-error 'simple-error (parse-namestring "~zotunknown/*.*")))
|
|
| 119 | + |
|
| 120 | +(define-test issue.266.pathname-tilde.1
|
|
| 121 | + (:tag :issues)
|
|
| 122 | + ;; Simple test for ~ in pathnames. Get a directory list using
|
|
| 123 | + ;; #P"~/*.*". This should produce exactly the same list as the
|
|
| 124 | + ;; #search-list P"home:*.*".
|
|
| 125 | + (let ((dir-home (directory #p"home:*.*" :truenamep nil :follow-links nil))
|
|
| 126 | + (dir-tilde (directory #p"~/*.*" :truenamep nil :follow-links nil)))
|
|
| 127 | + (assert-equal dir-tilde dir-home)))
|
|
| 128 | + |
|
| 129 | +(define-test issue.266.pathname-tilde.2
|
|
| 130 | + (:tag :issues)
|
|
| 131 | + ;; Simple test for ~ in pathnames. Get a directory list using
|
|
| 132 | + ;; #P"~user/*.*". This should produce exactly the same list as the
|
|
| 133 | + ;; #search-list P"home:*.*". We determine the user name via getuid
|
|
| 134 | + ;; #and getpwuid.
|
|
| 135 | + (let ((user-name (unix:user-info-name (unix:unix-getpwuid (unix:unix-getuid)))))
|
|
| 136 | + (assert-true user-name)
|
|
| 137 | + (let* ((dir-home (directory #p"home:*.*" :truenamep nil :follow-links nil))
|
|
| 138 | +
|
|
| 139 | + (dir-tilde (directory (concatenate 'string
|
|
| 140 | + "~"
|
|
| 141 | + user-name
|
|
| 142 | + "/*.*")
|
|
| 143 | + :truenamep nil :follow-links nil)))
|
|
| 144 | + (assert-equal dir-tilde dir-home)))) |