diff options
| author | Ludovic Courtès <ludo@gnu.org> | 2023-06-06 11:41:39 +0200 |
|---|---|---|
| committer | Ludovic Courtès <ludo@gnu.org> | 2023-06-06 11:54:39 +0200 |
| commit | 181951207339508789b28ba7cb914f983319920f (patch) | |
| tree | a9747a37eb4fa7cf7dadb481df64b7856644ca0e | |
| parent | dc0c5d56ee04d8a2b57f316be7f95b9aca244ab5 (diff) | |
services: 'modify-services' preserves service ordering.
Fixes <https://issues.guix.gnu.org/63921>.
The regression was introduced in
dbbc7e946131ba257728f1d05b96c4339b7ee88b, which changed the order of
services. As a result, someone using 'modify-services' could find
themselves with incorrect ordering of expressions in the "boot" script,
whereby the cleanup expressions would come after (execl ".../shepherd").
This, in turn, would lead shepherd to error out at boot with EADDRINUSE
on /var/run/shepherd/socket.
* gnu/services.scm (%delete-service, %apply-clauses): Remove.
(clause-alist): New macro.
(apply-clauses): New procedure.
(modify-services): Use it. Adjust docstring.
* tests/services.scm ("modify-services: do nothing"): Remove 'sort' call.
("modify-services: delete service"): Likewise, and add 't4' service.
("modify-services: change value"): Remove 'sort' call and fix expected value.
| -rw-r--r-- | gnu/services.scm | 93 | ||||
| -rw-r--r-- | tests/services.scm | 37 |
2 files changed, 80 insertions, 50 deletions
diff --git a/gnu/services.scm b/gnu/services.scm index a990d297c9c..5410d319715 100644 --- a/gnu/services.scm +++ b/gnu/services.scm | |||
| @@ -51,6 +51,7 @@ | |||
| 51 | #:use-module (srfi srfi-26) | 51 | #:use-module (srfi srfi-26) |
| 52 | #:use-module (srfi srfi-34) | 52 | #:use-module (srfi srfi-34) |
| 53 | #:use-module (srfi srfi-35) | 53 | #:use-module (srfi srfi-35) |
| 54 | #:use-module (srfi srfi-71) | ||
| 54 | #:use-module (ice-9 vlist) | 55 | #:use-module (ice-9 vlist) |
| 55 | #:use-module (ice-9 match) | 56 | #:use-module (ice-9 match) |
| 56 | #:autoload (ice-9 pretty-print) (pretty-print) | 57 | #:autoload (ice-9 pretty-print) (pretty-print) |
| @@ -297,35 +298,65 @@ singleton service type NAME, of which the returned service is an instance." | |||
| 297 | (description "This is a simple service.")))) | 298 | (description "This is a simple service.")))) |
| 298 | (service type value))) | 299 | (service type value))) |
| 299 | 300 | ||
| 300 | (define (%delete-service kind services) | 301 | (define-syntax clause-alist |
| 301 | (let loop ((found #f) | 302 | (syntax-rules (=> delete) |
| 302 | (return '()) | 303 | "Build an alist of clauses. Each element has the form (KIND PROC LOC) |
| 303 | (services services)) | 304 | where PROC is the service transformation procedure to apply for KIND, and LOC |
| 305 | is the source location information." | ||
| 306 | ((_ (delete kind) rest ...) | ||
| 307 | (cons (list kind | ||
| 308 | (lambda (service) | ||
| 309 | #f) | ||
| 310 | (current-source-location)) | ||
| 311 | (clause-alist rest ...))) | ||
| 312 | ((_ (kind param => exp ...) rest ...) | ||
| 313 | (cons (list kind | ||
| 314 | (lambda (svc) | ||
| 315 | (let ((param (service-value svc))) | ||
| 316 | (service (service-kind svc) | ||
| 317 | (begin exp ...)))) | ||
| 318 | (current-source-location)) | ||
| 319 | (clause-alist rest ...))) | ||
| 320 | ((_) | ||
| 321 | '()))) | ||
| 322 | |||
| 323 | (define (apply-clauses clauses services) | ||
| 324 | "Apply CLAUSES, an alist as returned by 'clause-alist', to SERVICES, a list | ||
| 325 | of services. Use each clause at most once; raise an error if a clause was not | ||
| 326 | used." | ||
| 327 | (let loop ((services services) | ||
| 328 | (clauses clauses) | ||
| 329 | (result '())) | ||
| 304 | (match services | 330 | (match services |
| 305 | ('() | 331 | (() |
| 306 | (if found | 332 | (match clauses |
| 307 | (values return found) | 333 | (() ;all clauses fired, good |
| 308 | (raise (formatted-message | 334 | (reverse result)) |
| 335 | (((kind _ properties) _ ...) ;one or more clauses didn't match | ||
| 336 | (raise (make-compound-condition | ||
| 337 | (condition | ||
| 338 | (&error-location | ||
| 339 | (location (source-properties->location properties)))) | ||
| 340 | (formatted-message | ||
| 309 | (G_ "modify-services: service '~a' not found in service list") | 341 | (G_ "modify-services: service '~a' not found in service list") |
| 310 | (service-type-name kind))))) | 342 | (service-type-name kind))))))) |
| 311 | ((service . rest) | 343 | ((head . tail) |
| 312 | (if (eq? (service-kind service) kind) | 344 | (let ((service clauses |
| 313 | (loop service return rest) | 345 | (fold2 (lambda (clause service remainder) |
| 314 | (loop found (cons service return) rest)))))) | 346 | (match clause |
| 315 | 347 | ((kind proc properties) | |
| 316 | (define-syntax %apply-clauses | 348 | (if (eq? kind (service-kind service)) |
| 317 | (syntax-rules (=> delete) | 349 | (values (proc service) remainder) |
| 318 | ((_ ((delete kind) . rest) services) | 350 | (values service |
| 319 | (%apply-clauses rest (%delete-service kind services))) | 351 | (cons clause remainder)))))) |
| 320 | ((_ ((kind param => exp ...) . rest) services) | 352 | head |
| 321 | (call-with-values (lambda () (%delete-service kind services)) | 353 | '() |
| 322 | (lambda (svcs found) | 354 | clauses))) |
| 323 | (let ((param (service-value found))) | 355 | (loop tail |
| 324 | (cons (service (service-kind found) | 356 | (reverse clauses) |
| 325 | (begin exp ...)) | 357 | (if service |
| 326 | (%apply-clauses rest svcs)))))) | 358 | (cons service result) |
| 327 | ((_ () services) | 359 | result))))))) |
| 328 | services))) | ||
| 329 | 360 | ||
| 330 | (define-syntax modify-services | 361 | (define-syntax modify-services |
| 331 | (syntax-rules () | 362 | (syntax-rules () |
| @@ -358,11 +389,9 @@ Consider this example: | |||
| 358 | 389 | ||
| 359 | It changes the configuration of the GUIX-SERVICE-TYPE instance, and that of | 390 | It changes the configuration of the GUIX-SERVICE-TYPE instance, and that of |
| 360 | all the MINGETTY-SERVICE-TYPE instances, and it deletes instances of the | 391 | all the MINGETTY-SERVICE-TYPE instances, and it deletes instances of the |
| 361 | UDEV-SERVICE-TYPE. | 392 | UDEV-SERVICE-TYPE." |
| 362 | 393 | ((_ services clauses ...) | |
| 363 | This is a shorthand for (filter-map (lambda (svc) ...) %base-services)." | 394 | (apply-clauses (clause-alist clauses ...) services)))) |
| 364 | ((_ services . clauses) | ||
| 365 | (%apply-clauses clauses services)))) | ||
| 366 | 395 | ||
| 367 | 396 | ||
| 368 | ;;; | 397 | ;;; |
diff --git a/tests/services.scm b/tests/services.scm index 8cdb1b2a314..20ff4d317e8 100644 --- a/tests/services.scm +++ b/tests/services.scm | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | ;;; GNU Guix --- Functional package management for GNU | 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2015-2019, 2022 Ludovic Courtès <ludo@gnu.org> | 2 | ;;; Copyright © 2015-2019, 2022, 2023 Ludovic Courtès <ludo@gnu.org> |
| 3 | ;;; | 3 | ;;; |
| 4 | ;;; This file is part of GNU Guix. | 4 | ;;; This file is part of GNU Guix. |
| 5 | ;;; | 5 | ;;; |
| @@ -287,7 +287,7 @@ | |||
| 287 | (x x)))) | 287 | (x x)))) |
| 288 | 288 | ||
| 289 | (test-equal "modify-services: do nothing" | 289 | (test-equal "modify-services: do nothing" |
| 290 | '(1 2 3) | 290 | '(1 2 3) ;note: service order must be preserved |
| 291 | (let* ((t1 (service-type (name 't1) | 291 | (let* ((t1 (service-type (name 't1) |
| 292 | (extensions '()) | 292 | (extensions '()) |
| 293 | (description ""))) | 293 | (description ""))) |
| @@ -298,12 +298,11 @@ | |||
| 298 | (extensions '()) | 298 | (extensions '()) |
| 299 | (description ""))) | 299 | (description ""))) |
| 300 | (services (list (service t1 1) (service t2 2) (service t3 3)))) | 300 | (services (list (service t1 1) (service t2 2) (service t3 3)))) |
| 301 | (sort (map service-value | 301 | (map service-value |
| 302 | (modify-services services)) | 302 | (modify-services services)))) |
| 303 | <))) | ||
| 304 | 303 | ||
| 305 | (test-equal "modify-services: delete service" | 304 | (test-equal "modify-services: delete service" |
| 306 | '(1) | 305 | '(1 4) ;note: service order must be preserved |
| 307 | (let* ((t1 (service-type (name 't1) | 306 | (let* ((t1 (service-type (name 't1) |
| 308 | (extensions '()) | 307 | (extensions '()) |
| 309 | (description ""))) | 308 | (description ""))) |
| @@ -313,12 +312,15 @@ | |||
| 313 | (t3 (service-type (name 't3) | 312 | (t3 (service-type (name 't3) |
| 314 | (extensions '()) | 313 | (extensions '()) |
| 315 | (description ""))) | 314 | (description ""))) |
| 316 | (services (list (service t1 1) (service t2 2) (service t3 3)))) | 315 | (t4 (service-type (name 't4) |
| 317 | (sort (map service-value | 316 | (extensions '()) |
| 318 | (modify-services services | 317 | (description ""))) |
| 319 | (delete t3) | 318 | (services (list (service t1 1) (service t2 2) |
| 320 | (delete t2))) | 319 | (service t3 3) (service t4 4)))) |
| 321 | <))) | 320 | (map service-value |
| 321 | (modify-services services | ||
| 322 | (delete t3) | ||
| 323 | (delete t2))))) | ||
| 322 | 324 | ||
| 323 | (test-error "modify-services: delete non-existing service" | 325 | (test-error "modify-services: delete non-existing service" |
| 324 | #t | 326 | #t |
| @@ -336,7 +338,7 @@ | |||
| 336 | (delete t3)))) | 338 | (delete t3)))) |
| 337 | 339 | ||
| 338 | (test-equal "modify-services: change value" | 340 | (test-equal "modify-services: change value" |
| 339 | '(2 11 33) | 341 | '(11 2 33) ;note: service order must be preserved |
| 340 | (let* ((t1 (service-type (name 't1) | 342 | (let* ((t1 (service-type (name 't1) |
| 341 | (extensions '()) | 343 | (extensions '()) |
| 342 | (description ""))) | 344 | (description ""))) |
| @@ -347,11 +349,10 @@ | |||
| 347 | (extensions '()) | 349 | (extensions '()) |
| 348 | (description ""))) | 350 | (description ""))) |
| 349 | (services (list (service t1 1) (service t2 2) (service t3 3)))) | 351 | (services (list (service t1 1) (service t2 2) (service t3 3)))) |
| 350 | (sort (map service-value | 352 | (map service-value |
| 351 | (modify-services services | 353 | (modify-services services |
| 352 | (t1 value => 11) | 354 | (t1 value => 11) |
| 353 | (t3 value => 33))) | 355 | (t3 value => 33))))) |
| 354 | <))) | ||
| 355 | 356 | ||
| 356 | (test-error "modify-services: change value for non-existing service" | 357 | (test-error "modify-services: change value for non-existing service" |
| 357 | #t | 358 | #t |
