* 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'.
---
doc/guile-netlink.texi | 8 ++++
ip/link.scm | 102 ++++++++++++++++++++++++++++++++++-------
2 files changed, 94 insertions(+), 16 deletions(-)
diff --git a/doc/guile-netlink.texi b/doc/guile-netlink.texi
index 4dbeafe..3355c27 100644
--- a/doc/guile-netlink.texi
+++ b/doc/guile-netlink.texi
@@ -567,6 +567,14 @@ Returns the list of existing links in the system, as a list of @code{}
objects.
@end deffn
+@deffn {Scheme Procedure} wait-for-link @var{name} [#:blocking? #t]
+Wait until a link called @var{name} (a string such as @code{"ens3"}) shows
+up.
+
+When @var{blocking?} is false, use a non-blocking socket and cooperate via
+@code{current-read-waiter}---useful when using Fibers.
+@end deffn
+
@deffn {Sceme Procedure} print-link @var{link}
Display @var{link} on the standard output, using a format similar to
@command{ip link} from @code{iproute2}.
diff --git a/ip/link.scm b/ip/link.scm
index 7e0ae6b..1323444 100644
--- a/ip/link.scm
+++ b/ip/link.scm
@@ -1,7 +1,8 @@
;;;; This file is part of Guile Netlink
;;;;
;;;; Copyright (C) 2021 Julien Lepiller
-;;;;
+;;;; Copyright (C) 2023 Ludovic Courtès
+;;;;
;;;; This library is free software: you can redistribute it and/or modify
;;;; it under the terms of the GNU General Public License as published by
;;;; the Free Software Foundation, either version 3 of the License, or
@@ -31,12 +32,14 @@
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
+ #:use-module (srfi srfi-71)
#:export (link-add
link-del
link-set
link-show
link-name->index
get-links
+ wait-for-link
print-link
make-link link?
@@ -59,24 +62,35 @@
(addr link-addr)
(brd link-brd))
+(define (message->event+link msg)
+ "If MSG relates to a link event, return two values: its kind (e.g.,
+RTM_NEWLINK) and its associated value. Otherwise return #f and #f."
+ (if (memv (message-kind msg)
+ (list RTM_NEWLINK
+ RTM_DELLINK
+ RTM_SETLINK))
+ (values (message-kind msg)
+ (let* ((data (message-data msg))
+ (attrs (link-message-attrs data)))
+ (make-link (get-attr attrs IFLA_IFNAME)
+ (link-message-index data)
+ (link-message-kind data)
+ (map int->device-flags (split-flags (link-message-flags data)))
+ (get-attr attrs IFLA_MTU)
+ (get-attr attrs IFLA_QDISC)
+ (get-attr attrs IFLA_OPERSTATE)
+ (get-attr attrs IFLA_LINKMODE)
+ (get-attr attrs IFLA_GROUP)
+ (get-attr attrs IFLA_TXQLEN)
+ (get-attr attrs IFLA_ADDRESS)
+ (get-attr attrs IFLA_BROADCAST))))
+ (values #f #f)))
+
(define (new-link-message->link msg)
"If MSG has type 'RTM_NEWLINK', return the corresponding object.
Otherwise return #f."
- (and (eqv? (message-kind msg) RTM_NEWLINK)
- (let* ((data (message-data msg))
- (attrs (link-message-attrs data)))
- (make-link (get-attr attrs IFLA_IFNAME)
- (link-message-index data)
- (link-message-kind data)
- (map int->device-flags (split-flags (link-message-flags data)))
- (get-attr attrs IFLA_MTU)
- (get-attr attrs IFLA_QDISC)
- (get-attr attrs IFLA_OPERSTATE)
- (get-attr attrs IFLA_LINKMODE)
- (get-attr attrs IFLA_GROUP)
- (get-attr attrs IFLA_TXQLEN)
- (get-attr attrs IFLA_ADDRESS)
- (get-attr attrs IFLA_BROADCAST)))))
+ (let ((kind link (message->event+link msg)))
+ (and (eqv? kind RTM_NEWLINK) link)))
(define (get-links)
(define request-num (random 65535))
@@ -390,3 +404,59 @@ balance-rr|active-backup|balance-xor|broadcast|802.3ad|balance-tlb|balance-alb"
(let ((answer (receive-and-decode-msg sock %default-route-decoder)))
(close-port sock)
(answer-ok? (last answer)))))
+
+(define* (monitor-links proc init terminate? ;TODO: Make public?
+ #:key (blocking? #t))
+ "Wait for link events until @var{terminate?} returns true. Call @var{init}
+with the initial list of links; use its result as the initial state. From
+then on, call @code{(@var{proc} @var{event} @var{link} @var{state})} where
+@var{event} is a constant such as @code{RTM_NEWLINK} and @var{link} is the
+corresponding link. Return the final state.
+
+When @code{blocking?} is false, use a non-blocking socket and cooperate via
+@code{current-read-waiter}---useful when using Fibers."
+ (define request-num (random 65536))
+ (define message
+ (make-message
+ RTM_GETLINK
+ (logior NLM_F_REQUEST NLM_F_DUMP)
+ request-num
+ 0
+ (make-link-message AF_UNSPEC 0 0 0 0 '())))
+
+ (let ((sock (connect-route #:flags (if blocking? 0 SOCK_NONBLOCK))))
+ ;; Subscribe to the "link" group.
+ (add-socket-membership sock RTNLGRP_LINK)
+
+ (send-msg message sock)
+ (let* ((answer (receive-and-decode-msg sock %default-route-decoder))
+ (links (filter-map new-link-message->link answer)))
+ (let loop ((state (init links)))
+ (if (terminate? state)
+ (begin
+ (close-port sock)
+ state)
+ (loop (fold (lambda (msg state)
+ (let ((event link (message->event+link msg)))
+ (proc event link state)))
+ state
+ (receive-and-decode-msg sock %default-route-decoder))))))))
+
+
+(define* (wait-for-link name #:key (blocking? #t))
+ "Wait until a link called @var{name} (a string such as @code{\"ens3\"}) shows
+up.
+
+When @var{blocking?} is false, use a non-blocking socket and cooperate via
+@code{current-read-waiter}---useful when using Fibers."
+ (monitor-links (lambda (event link result)
+ (and (= RTM_NEWLINK)
+ (string=? (link-name link) name)
+ link))
+ (lambda (links)
+ (find (lambda (link)
+ (string=? (link-name link) name))
+ links))
+ (lambda (link) ;if LINK is true, terminate
+ link)
+ #:blocking? blocking?))
--
2.40.1