link: Add 'wait-for-link'.

Ludovic Court??sTue May 23 20:46:52+0200 2023

9a72f95

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

567567
objects.
568568
@end deffn
569569
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+
570578
@deffn {Sceme Procedure} print-link @var{link}
571579
Display @var{link} on the standard output, using a format similar to
572580
@command{ip link} from @code{iproute2}.

ip/link.scm

11
;;;; This file is part of Guile Netlink
22
;;;;
33
;;;; Copyright (C) 2021 Julien Lepiller <julien@lepiller.eu>
4-
;;;; 
4+
;;;; Copyright (C) 2023 Ludovic Court??s <ludo@gnu.org>
5+
;;;;
56
;;;; This library is free software: you can redistribute it and/or modify
67
;;;; it under the terms of the GNU General Public License as published by
78
;;;; the Free Software Foundation, either version 3 of the License, or

3132
  #:use-module (srfi srfi-9)
3233
  #:use-module (srfi srfi-34)
3334
  #:use-module (srfi srfi-35)
35+
  #:use-module (srfi srfi-71)
3436
  #:export (link-add
3537
            link-del
3638
            link-set
3739
            link-show
3840
            link-name->index
3941
            get-links
42+
            wait-for-link
4043
            print-link
4144
4245
            <link> make-link link?

5962
  (addr  link-addr)
6063
  (brd   link-brd))
6164
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+
6289
(define (new-link-message->link msg)
6390
  "If MSG has type 'RTM_NEWLINK', return the corresponding <link> object.
6491
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)))
8094
8195
(define (get-links)
8296
  (define request-num (random 65535))

390404
    (let ((answer (receive-and-decode-msg sock %default-route-decoder)))
391405
      (close-port sock)
392406
      (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?))