link: Add 'wait-for-link'. * ip/link.scm (message->event+link): New procedure. (new-link-message->link): Use it. (monitor-links, wait-for-link): New procedures. * doc/guile-netlink.texi (Link): Document 'wait-for-link'. Signed-off-by: Julien Lepiller <julien@lepiller.eu>
doc/guile-netlink.texi
| 567 | 567 | objects. | |
| 568 | 568 | @end deffn | |
| 569 | 569 | ||
| 570 | + | @deffn {Scheme Procedure} wait-for-link @var{name} [#:blocking? #t] | |
| 571 | + | Wait until a link called @var{name} (a string such as @code{"ens3"}) shows | |
| 572 | + | up. | |
| 573 | + | ||
| 574 | + | When @var{blocking?} is false, use a non-blocking socket and cooperate via | |
| 575 | + | @code{current-read-waiter}---useful when using Fibers. | |
| 576 | + | @end deffn | |
| 577 | + | ||
| 570 | 578 | @deffn {Sceme Procedure} print-link @var{link} | |
| 571 | 579 | Display @var{link} on the standard output, using a format similar to | |
| 572 | 580 | @command{ip link} from @code{iproute2}. |
ip/link.scm
| 1 | 1 | ;;;; This file is part of Guile Netlink | |
| 2 | 2 | ;;;; | |
| 3 | 3 | ;;;; Copyright (C) 2021 Julien Lepiller <julien@lepiller.eu> | |
| 4 | - | ;;;; | |
| 4 | + | ;;;; Copyright (C) 2023 Ludovic Court??s <ludo@gnu.org> | |
| 5 | + | ;;;; | |
| 5 | 6 | ;;;; This library is free software: you can redistribute it and/or modify | |
| 6 | 7 | ;;;; it under the terms of the GNU General Public License as published by | |
| 7 | 8 | ;;;; the Free Software Foundation, either version 3 of the License, or | |
… | |||
| 31 | 32 | #:use-module (srfi srfi-9) | |
| 32 | 33 | #:use-module (srfi srfi-34) | |
| 33 | 34 | #:use-module (srfi srfi-35) | |
| 35 | + | #:use-module (srfi srfi-71) | |
| 34 | 36 | #:export (link-add | |
| 35 | 37 | link-del | |
| 36 | 38 | link-set | |
| 37 | 39 | link-show | |
| 38 | 40 | link-name->index | |
| 39 | 41 | get-links | |
| 42 | + | wait-for-link | |
| 40 | 43 | print-link | |
| 41 | 44 | ||
| 42 | 45 | <link> make-link link? | |
… | |||
| 59 | 62 | (addr link-addr) | |
| 60 | 63 | (brd link-brd)) | |
| 61 | 64 | ||
| 65 | + | (define (message->event+link msg) | |
| 66 | + | "If MSG relates to a link event, return two values: its kind (e.g., | |
| 67 | + | RTM_NEWLINK) and its associated <link> value. Otherwise return #f and #f." | |
| 68 | + | (if (memv (message-kind msg) | |
| 69 | + | (list RTM_NEWLINK | |
| 70 | + | RTM_DELLINK | |
| 71 | + | RTM_SETLINK)) | |
| 72 | + | (values (message-kind msg) | |
| 73 | + | (let* ((data (message-data msg)) | |
| 74 | + | (attrs (link-message-attrs data))) | |
| 75 | + | (make-link (get-attr attrs IFLA_IFNAME) | |
| 76 | + | (link-message-index data) | |
| 77 | + | (link-message-kind data) | |
| 78 | + | (map int->device-flags (split-flags (link-message-flags data))) | |
| 79 | + | (get-attr attrs IFLA_MTU) | |
| 80 | + | (get-attr attrs IFLA_QDISC) | |
| 81 | + | (get-attr attrs IFLA_OPERSTATE) | |
| 82 | + | (get-attr attrs IFLA_LINKMODE) | |
| 83 | + | (get-attr attrs IFLA_GROUP) | |
| 84 | + | (get-attr attrs IFLA_TXQLEN) | |
| 85 | + | (get-attr attrs IFLA_ADDRESS) | |
| 86 | + | (get-attr attrs IFLA_BROADCAST)))) | |
| 87 | + | (values #f #f))) | |
| 88 | + | ||
| 62 | 89 | (define (new-link-message->link msg) | |
| 63 | 90 | "If MSG has type 'RTM_NEWLINK', return the corresponding <link> object. | |
| 64 | 91 | Otherwise return #f." | |
| 65 | - | (and (eqv? (message-kind msg) RTM_NEWLINK) | |
| 66 | - | (let* ((data (message-data msg)) | |
| 67 | - | (attrs (link-message-attrs data))) | |
| 68 | - | (make-link (get-attr attrs IFLA_IFNAME) | |
| 69 | - | (link-message-index data) | |
| 70 | - | (link-message-kind data) | |
| 71 | - | (map int->device-flags (split-flags (link-message-flags data))) | |
| 72 | - | (get-attr attrs IFLA_MTU) | |
| 73 | - | (get-attr attrs IFLA_QDISC) | |
| 74 | - | (get-attr attrs IFLA_OPERSTATE) | |
| 75 | - | (get-attr attrs IFLA_LINKMODE) | |
| 76 | - | (get-attr attrs IFLA_GROUP) | |
| 77 | - | (get-attr attrs IFLA_TXQLEN) | |
| 78 | - | (get-attr attrs IFLA_ADDRESS) | |
| 79 | - | (get-attr attrs IFLA_BROADCAST))))) | |
| 92 | + | (let ((kind link (message->event+link msg))) | |
| 93 | + | (and (eqv? kind RTM_NEWLINK) link))) | |
| 80 | 94 | ||
| 81 | 95 | (define (get-links) | |
| 82 | 96 | (define request-num (random 65535)) | |
… | |||
| 390 | 404 | (let ((answer (receive-and-decode-msg sock %default-route-decoder))) | |
| 391 | 405 | (close-port sock) | |
| 392 | 406 | (answer-ok? (last answer))))) | |
| 407 | + | ||
| 408 | + | (define* (monitor-links proc init terminate? ;TODO: Make public? | |
| 409 | + | #:key (blocking? #t)) | |
| 410 | + | "Wait for link events until @var{terminate?} returns true. Call @var{init} | |
| 411 | + | with the initial list of links; use its result as the initial state. From | |
| 412 | + | then on, call @code{(@var{proc} @var{event} @var{link} @var{state})} where | |
| 413 | + | @var{event} is a constant such as @code{RTM_NEWLINK} and @var{link} is the | |
| 414 | + | corresponding link. Return the final state. | |
| 415 | + | ||
| 416 | + | When @code{blocking?} is false, use a non-blocking socket and cooperate via | |
| 417 | + | @code{current-read-waiter}---useful when using Fibers." | |
| 418 | + | (define request-num (random 65536)) | |
| 419 | + | (define message | |
| 420 | + | (make-message | |
| 421 | + | RTM_GETLINK | |
| 422 | + | (logior NLM_F_REQUEST NLM_F_DUMP) | |
| 423 | + | request-num | |
| 424 | + | 0 | |
| 425 | + | (make-link-message AF_UNSPEC 0 0 0 0 '()))) | |
| 426 | + | ||
| 427 | + | (let ((sock (connect-route #:flags (if blocking? 0 SOCK_NONBLOCK)))) | |
| 428 | + | ;; Subscribe to the "link" group. | |
| 429 | + | (add-socket-membership sock RTNLGRP_LINK) | |
| 430 | + | ||
| 431 | + | (send-msg message sock) | |
| 432 | + | (let* ((answer (receive-and-decode-msg sock %default-route-decoder)) | |
| 433 | + | (links (filter-map new-link-message->link answer))) | |
| 434 | + | (let loop ((state (init links))) | |
| 435 | + | (if (terminate? state) | |
| 436 | + | (begin | |
| 437 | + | (close-port sock) | |
| 438 | + | state) | |
| 439 | + | (loop (fold (lambda (msg state) | |
| 440 | + | (let ((event link (message->event+link msg))) | |
| 441 | + | (proc event link state))) | |
| 442 | + | state | |
| 443 | + | (receive-and-decode-msg sock %default-route-decoder)))))))) | |
| 444 | + | ||
| 445 | + | ||
| 446 | + | (define* (wait-for-link name #:key (blocking? #t)) | |
| 447 | + | "Wait until a link called @var{name} (a string such as @code{\"ens3\"}) shows | |
| 448 | + | up. | |
| 449 | + | ||
| 450 | + | When @var{blocking?} is false, use a non-blocking socket and cooperate via | |
| 451 | + | @code{current-read-waiter}---useful when using Fibers." | |
| 452 | + | (monitor-links (lambda (event link result) | |
| 453 | + | (and (= RTM_NEWLINK) | |
| 454 | + | (string=? (link-name link) name) | |
| 455 | + | link)) | |
| 456 | + | (lambda (links) | |
| 457 | + | (find (lambda (link) | |
| 458 | + | (string=? (link-name link) name)) | |
| 459 | + | links)) | |
| 460 | + | (lambda (link) ;if LINK is true, terminate | |
| 461 | + | link) | |
| 462 | + | #:blocking? blocking?)) | |