···7777 help="vlans to span by the driver",
7878 )
7979 arg_parser.add_argument(
8080+ "--global-timeout",
8181+ type=int,
8282+ metavar="GLOBAL_TIMEOUT",
8383+ action=EnvDefault,
8484+ envvar="globalTimeout",
8585+ help="Timeout in seconds for the whole test",
8686+ )
8787+ arg_parser.add_argument(
8088 "-o",
8189 "--output_directory",
8290 help="""The path to the directory where outputs copied from the VM will be placed.
···103111 args.testscript.read_text(),
104112 args.output_directory.resolve(),
105113 args.keep_vm_state,
114114+ args.global_timeout,
106115 ) as driver:
107116 if args.interactive:
108117 history_dir = os.getcwd()
+25
nixos/lib/test-driver/test_driver/driver.py
···11import os
22import re
33+import signal
34import tempfile
55+import threading
46from contextlib import contextmanager
57from pathlib import Path
68from typing import Any, Callable, ContextManager, Dict, Iterator, List, Optional, Union
···4143 vlans: List[VLan]
4244 machines: List[Machine]
4345 polling_conditions: List[PollingCondition]
4646+ global_timeout: int
4747+ race_timer: threading.Timer
44484549 def __init__(
4650 self,
···4953 tests: str,
5054 out_dir: Path,
5155 keep_vm_state: bool = False,
5656+ global_timeout: int = 24 * 60 * 60 * 7,
5257 ):
5358 self.tests = tests
5459 self.out_dir = out_dir
6060+ self.global_timeout = global_timeout
6161+ self.race_timer = threading.Timer(global_timeout, self.terminate_test)
55625663 tmp_dir = get_tmp_dir()
5764···82898390 def __exit__(self, *_: Any) -> None:
8491 with rootlog.nested("cleanup"):
9292+ self.race_timer.cancel()
8593 for machine in self.machines:
8694 machine.release()
8795···144152145153 def run_tests(self) -> None:
146154 """Run the test script (for non-interactive test runs)"""
155155+ rootlog.info(
156156+ f"Test will time out and terminate in {self.global_timeout} seconds"
157157+ )
158158+ self.race_timer.start()
147159 self.test_script()
148160 # TODO: Collect coverage data
149161 for machine in self.machines:
···161173 with rootlog.nested("wait for all VMs to finish"):
162174 for machine in self.machines:
163175 machine.wait_for_shutdown()
176176+ self.race_timer.cancel()
177177+178178+ def terminate_test(self) -> None:
179179+ # This will be usually running in another thread than
180180+ # the thread actually executing the test script.
181181+ with rootlog.nested("timeout reached; test terminating..."):
182182+ for machine in self.machines:
183183+ machine.release()
184184+ # As we cannot `sys.exit` from another thread
185185+ # We can at least force the main thread to get SIGTERM'ed.
186186+ # This will prevent any user who caught all the exceptions
187187+ # to swallow them and prevent itself from terminating.
188188+ os.kill(os.getpid(), signal.SIGTERM)
164189165190 def create_machine(self, args: Dict[str, Any]) -> Machine:
166191 tmp_dir = get_tmp_dir()
···21212222 virtualisation.azureImage.diskSize = 2500;
23232424- system.stateVersion = "20.03";
2524 boot.kernelPackages = pkgs.linuxPackages_latest;
26252726 # test user doesn't have a password
···22# your system. Help is available in the configuration.nix(5) man page
33# and in the NixOS manual (accessible by running ‘nixos-help’).
4455-{ config, pkgs, lib, ... }:
55+{ config, pkgs, lib, modulesPath, ... }:
6677{
88 imports =
99 [
1010 # Include the default lxd configuration.
1111- ../../../modules/virtualisation/lxc-container.nix
1111+ "${modulesPath}/modules/virtualisation/lxc-container.nix"
1212 # Include the container-specific autogenerated configuration.
1313 ./lxd.nix
1414 ];
···1616 networking.useDHCP = false;
1717 networking.interfaces.eth0.useDHCP = true;
18181919- system.stateVersion = "21.05"; # Did you read the comment?
1919+ system.stateVersion = "@stateVersion@"; # Did you read the comment?
2020}
···22# your system. Help is available in the configuration.nix(5) man page
33# and in the NixOS manual (accessible by running ‘nixos-help’).
4455-{ config, pkgs, lib, ... }:
55+{ config, pkgs, lib, modulesPath, ... }:
6677{
88 imports =
99 [
1010 # Include the default lxd configuration.
1111- ../../../modules/virtualisation/lxd-virtual-machine.nix
1111+ "${modulesPath}/virtualisation/lxd-virtual-machine.nix"
1212 # Include the container-specific autogenerated configuration.
1313 ./lxd.nix
1414 ];
···1616 networking.useDHCP = false;
1717 networking.interfaces.eth0.useDHCP = true;
18181919- system.stateVersion = "23.05"; # Did you read the comment?
1919+ system.stateVersion = "@stateVersion@"; # Did you read the comment?
2020}
···224224 # accidentally delete configuration.nix.
225225 # system.copySystemConfiguration = true;
226226227227- # This value determines the NixOS release from which the default
228228- # settings for stateful data, like file locations and database versions
229229- # on your system were taken. It's perfectly fine and recommended to leave
230230- # this value at the release version of the first install of this system.
231231- # Before changing this value read the documentation for this option
232232- # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
227227+ # This option defines the first version of NixOS you have installed on this particular machine,
228228+ # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
229229+ #
230230+ # Most users should NEVER change this value after the initial install, for any reason,
231231+ # even if you've upgraded your system to a new NixOS release.
232232+ #
233233+ # This value does NOT affect the Nixpkgs version your packages and OS are pulled from,
234234+ # so changing it will NOT upgrade your system.
235235+ #
236236+ # This value being lower than the current NixOS release does NOT mean your system is
237237+ # out of date, out of support, or vulnerable.
238238+ #
239239+ # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
240240+ # and migrated your data accordingly.
241241+ #
242242+ # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
233243 system.stateVersion = "${config.system.nixos.release}"; # Did you read the comment?
234244235245 }
···121121 default = cfg.release;
122122 defaultText = literalExpression "config.${opt.release}";
123123 description = lib.mdDoc ''
124124- Every once in a while, a new NixOS release may change
125125- configuration defaults in a way incompatible with stateful
126126- data. For instance, if the default version of PostgreSQL
127127- changes, the new version will probably be unable to read your
128128- existing databases. To prevent such breakage, you should set the
129129- value of this option to the NixOS release with which you want
130130- to be compatible. The effect is that NixOS will use
131131- defaults corresponding to the specified release (such as using
132132- an older version of PostgreSQL).
133133- It’s perfectly fine and recommended to leave this value at the
134134- release version of the first install of this system.
135135- Changing this option will not upgrade your system. In fact it
136136- is meant to stay constant exactly when you upgrade your system.
137137- You should only bump this option, if you are sure that you can
138138- or have migrated all state on your system which is affected
139139- by this option.
124124+ This option defines the first version of NixOS you have installed on this particular machine,
125125+ and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
126126+127127+ For example, if NixOS version XX.YY ships with AwesomeDB version N by default, and is then
128128+ upgraded to version XX.YY+1, which ships AwesomeDB version N+1, the existing databases
129129+ may no longer be compatible, causing applications to fail, or even leading to data loss.
130130+131131+ The `stateVersion` mechanism avoids this situation by making the default version of such packages
132132+ conditional on the first version of NixOS you've installed (encoded in `stateVersion`), instead of
133133+ simply always using the latest one.
134134+135135+ Note that this generally only affects applications that can't upgrade their data automatically -
136136+ applications and services supporting automatic migrations will remain on latest versions when
137137+ you upgrade.
138138+139139+ Most users should **never** change this value after the initial install, for any reason,
140140+ even if you've upgraded your system to a new NixOS release.
141141+142142+ This value does **not** affect the Nixpkgs version your packages and OS are pulled from,
143143+ so changing it will **not** upgrade your system.
144144+145145+ This value being lower than the current NixOS release does **not** mean your system is
146146+ out of date, out of support, or vulnerable.
147147+148148+ Do **not** change this value unless you have manually inspected all the changes it would
149149+ make to your configuration, and migrated your data accordingly.
140150 '';
141151 };
142152
+1-1
nixos/modules/services/networking/gvpe.nix
···29293030 export PATH=$PATH:${pkgs.iproute2}/sbin
31313232- ip link set $IFNAME up
3232+ ip link set dev $IFNAME up
3333 ip address add ${cfg.ipAddress} dev $IFNAME
3434 ip route add ${cfg.subnet} dev $IFNAME
3535
+72-14
nixos/modules/services/networking/ssh/sshd.nix
···1212 then cfgc.package
1313 else pkgs.buildPackages.openssh;
14141515- # reports boolean as yes / no
1616- mkValueStringSshd = with lib; v:
1717- if isInt v then toString v
1818- else if isString v then v
1919- else if true == v then "yes"
2020- else if false == v then "no"
2121- else if isList v then concatStringsSep "," v
2222- else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
2323-2415 # dont use the "=" operator
2525- settingsFormat = (pkgs.formats.keyValue {
2626- mkKeyValue = lib.generators.mkKeyValueDefault {
2727- mkValueString = mkValueStringSshd;
2828- } " ";});
1616+ settingsFormat =
1717+ let
1818+ # reports boolean as yes / no
1919+ mkValueString = with lib; v:
2020+ if isInt v then toString v
2121+ else if isString v then v
2222+ else if true == v then "yes"
2323+ else if false == v then "no"
2424+ else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
29253030- configFile = settingsFormat.generate "sshd.conf-settings" cfg.settings;
2626+ base = pkgs.formats.keyValue {
2727+ mkKeyValue = lib.generators.mkKeyValueDefault { inherit mkValueString; } " ";
2828+ };
2929+ # OpenSSH is very inconsistent with options that can take multiple values.
3030+ # For some of them, they can simply appear multiple times and are appended, for others the
3131+ # values must be separated by whitespace or even commas.
3232+ # Consult either sshd_config(5) or, as last resort, the OpehSSH source for parsing
3333+ # the options at servconf.c:process_server_config_line_depth() to determine the right "mode"
3434+ # for each. But fortunaly this fact is documented for most of them in the manpage.
3535+ commaSeparated = [ "Ciphers" "KexAlgorithms" "Macs" ];
3636+ spaceSeparated = [ "AuthorizedKeysFile" "AllowGroups" "AllowUsers" "DenyGroups" "DenyUsers" ];
3737+ in {
3838+ inherit (base) type;
3939+ generate = name: value:
4040+ let transformedValue = mapAttrs (key: val:
4141+ if isList val then
4242+ if elem key commaSeparated then concatStringsSep "," val
4343+ else if elem key spaceSeparated then concatStringsSep " " val
4444+ else throw "list value for unknown key ${key}: ${(lib.generators.toPretty {}) val}"
4545+ else
4646+ val
4747+ ) value;
4848+ in
4949+ base.generate name transformedValue;
5050+ };
5151+5252+ configFile = settingsFormat.generate "sshd.conf-settings" (filterAttrs (n: v: v != null) cfg.settings);
3153 sshconf = pkgs.runCommand "sshd.conf-final" { } ''
3254 cat ${configFile} - >$out <<EOL
3355 ${cfg.extraConfig}
···429451 <https://stribika.github.io/2015/01/04/secure-secure-shell.html>
430452 and
431453 <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
454454+ '';
455455+ };
456456+ AllowUsers = mkOption {
457457+ type = with types; nullOr (listOf str);
458458+ default = null;
459459+ description = lib.mdDoc ''
460460+ If specified, login is allowed only for the listed users.
461461+ See {manpage}`sshd_config(5)` for details.
462462+ '';
463463+ };
464464+ DenyUsers = mkOption {
465465+ type = with types; nullOr (listOf str);
466466+ default = null;
467467+ description = lib.mdDoc ''
468468+ If specified, login is denied for all listed users. Takes
469469+ precedence over [](#opt-services.openssh.settings.AllowUsers).
470470+ See {manpage}`sshd_config(5)` for details.
471471+ '';
472472+ };
473473+ AllowGroups = mkOption {
474474+ type = with types; nullOr (listOf str);
475475+ default = null;
476476+ description = lib.mdDoc ''
477477+ If specified, login is allowed only for users part of the
478478+ listed groups.
479479+ See {manpage}`sshd_config(5)` for details.
480480+ '';
481481+ };
482482+ DenyGroups = mkOption {
483483+ type = with types; nullOr (listOf str);
484484+ default = null;
485485+ description = lib.mdDoc ''
486486+ If specified, login is denied for all users part of the listed
487487+ groups. Takes precedence over
488488+ [](#opt-services.openssh.settings.AllowGroups). See
489489+ {manpage}`sshd_config(5)` for details.
432490 '';
433491 };
434492 };
···230230231231 system.activationScripts.stdio = ""; # obsolete
232232 system.activationScripts.var = ""; # obsolete
233233- system.activationScripts.specialfs = ""; # obsolete
234233235234 systemd.tmpfiles.rules = [
236235 # Prevent the current configuration from being garbage-collected.
···250249 else ''
251250 rm -f /usr/bin/env
252251 rmdir --ignore-fail-on-non-empty /usr/bin /usr
252252+ '';
253253+254254+ system.activationScripts.specialfs =
255255+ ''
256256+ specialMount() {
257257+ local device="$1"
258258+ local mountPoint="$2"
259259+ local options="$3"
260260+ local fsType="$4"
261261+262262+ if mountpoint -q "$mountPoint"; then
263263+ local options="remount,$options"
264264+ else
265265+ mkdir -p "$mountPoint"
266266+ chmod 0755 "$mountPoint"
267267+ fi
268268+ mount -t "$fsType" -o "$options" "$device" "$mountPoint"
269269+ }
270270+ source ${config.system.build.earlyMountScript}
253271 '';
254272255273 systemd.user = {
+3-3
nixos/modules/system/boot/initrd-network.nix
···138138 # Bring up all interfaces.
139139 for iface in ${dhcpIfShellExpr}; do
140140 echo "bringing up network interface $iface..."
141141- ip link set "$iface" up && ifaces="$ifaces $iface"
141141+ ip link set dev "$iface" up && ifaces="$ifaces $iface"
142142 done
143143144144 # Acquire DHCP leases.
···152152153153 boot.initrd.postMountCommands = mkIf cfg.flushBeforeStage2 ''
154154 for iface in $ifaces; do
155155- ip address flush "$iface"
156156- ip link set "$iface" down
155155+ ip address flush dev "$iface"
156156+ ip link set dev "$iface" down
157157 done
158158 '';
159159
···2828 SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}')
2929 for I in $SLAVES; do
3030 UPDATED=0
3131- ip link set "$I" nomaster
3131+ ip link set dev "$I" nomaster
3232 done
3333 [ "$UPDATED" -eq "1" ] && break
3434 done
3535- ip link set "${i}" down 2>/dev/null || true
3636- ip link del "${i}" 2>/dev/null || true
3535+ ip link set dev "${i}" down 2>/dev/null || true
3636+ ip link del dev "${i}" 2>/dev/null || true
3737 '';
38383939 # warn that these attributes are deprecated (2017-2-2)
···193193 state="/run/nixos/network/addresses/${i.name}"
194194 mkdir -p $(dirname "$state")
195195196196- ip link set "${i.name}" up
196196+ ip link set dev "${i.name}" up
197197198198 ${flip concatMapStrings ips (ip:
199199 let
···270270 ip tuntap add dev "${i.name}" mode "${i.virtualType}" user "${i.virtualOwner}"
271271 '';
272272 postStop = ''
273273- ip link del ${i.name} || true
273273+ ip link del dev ${i.name} || true
274274 '';
275275 };
276276···291291 script = ''
292292 # Remove Dead Interfaces
293293 echo "Removing old bridge ${n}..."
294294- ip link show dev "${n}" >/dev/null 2>&1 && ip link del "${n}"
294294+ ip link show dev "${n}" >/dev/null 2>&1 && ip link del dev "${n}"
295295296296 echo "Adding bridge ${n}..."
297297 ip link add name "${n}" type bridge
298298299299 # Enslave child interfaces
300300 ${flip concatMapStrings v.interfaces (i: ''
301301- ip link set "${i}" master "${n}"
302302- ip link set "${i}" up
301301+ ip link set dev "${i}" master "${n}"
302302+ ip link set dev "${i}" up
303303 '')}
304304 # Save list of enslaved interfaces
305305 echo "${flip concatMapStrings v.interfaces (i: ''
···316316 for uri in qemu:///system lxc:///; do
317317 for dom in $(${pkgs.libvirt}/bin/virsh -c $uri list --name); do
318318 ${pkgs.libvirt}/bin/virsh -c $uri dumpxml "$dom" | \
319319- ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set ',target/@dev,' master ',source/@bridge,';')" | \
319319+ ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set dev ',target/@dev,' master ',source/@bridge,';')" | \
320320 ${pkgs.bash}/bin/bash
321321 done
322322 done
···328328 echo 2 >/sys/class/net/${n}/bridge/stp_state
329329 ''}
330330331331- ip link set "${n}" up
331331+ ip link set dev "${n}" up
332332 '';
333333 postStop = ''
334334- ip link set "${n}" down || true
335335- ip link del "${n}" || true
334334+ ip link set dev "${n}" down || true
335335+ ip link del dev "${n}" || true
336336 rm -f /run/${n}.interfaces
337337 '';
338338 reload = ''
339339 # Un-enslave child interfaces (old list of interfaces)
340340 for interface in `cat /run/${n}.interfaces`; do
341341- ip link set "$interface" nomaster up
341341+ ip link set dev "$interface" nomaster up
342342 done
343343344344 # Enslave child interfaces (new list of interfaces)
345345 ${flip concatMapStrings v.interfaces (i: ''
346346- ip link set "${i}" master "${n}"
347347- ip link set "${i}" up
346346+ ip link set dev "${i}" master "${n}"
347347+ ip link set dev "${i}" up
348348 '')}
349349 # Save list of enslaved interfaces
350350 echo "${flip concatMapStrings v.interfaces (i: ''
···395395 postStop = ''
396396 echo "Cleaning Open vSwitch ${n}"
397397 echo "Shutting down internal ${n} interface"
398398- ip link set ${n} down || true
398398+ ip link set dev ${n} down || true
399399 echo "Deleting flows for ${n}"
400400 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
401401 echo "Deleting Open vSwitch ${n}"
···433433 while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done;
434434435435 # Bring up the bond and enslave the specified interfaces
436436- ip link set "${n}" up
436436+ ip link set dev "${n}" up
437437 ${flip concatMapStrings v.interfaces (i: ''
438438- ip link set "${i}" down
439439- ip link set "${i}" master "${n}"
438438+ ip link set dev "${i}" down
439439+ ip link set dev "${i}" master "${n}"
440440 '')}
441441 '';
442442 postStop = destroyBond n;
···457457 path = [ pkgs.iproute2 ];
458458 script = ''
459459 # Remove Dead Interfaces
460460- ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
460460+ ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
461461 ip link add link "${v.interface}" name "${n}" type macvlan \
462462 ${optionalString (v.mode != null) "mode ${v.mode}"}
463463- ip link set "${n}" up
463463+ ip link set dev "${n}" up
464464 '';
465465 postStop = ''
466466- ip link delete "${n}" || true
466466+ ip link delete dev "${n}" || true
467467 '';
468468 });
469469···515515 path = [ pkgs.iproute2 ];
516516 script = ''
517517 # Remove Dead Interfaces
518518- ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
518518+ ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
519519 ip link add name "${n}" type sit \
520520 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
521521 ${optionalString (v.local != null) "local \"${v.local}\""} \
···526526 optionalString (v.encapsulation.sourcePort != null)
527527 "encap-sport ${toString v.encapsulation.sourcePort}"
528528 }"}
529529- ip link set "${n}" up
529529+ ip link set dev "${n}" up
530530 '';
531531 postStop = ''
532532- ip link delete "${n}" || true
532532+ ip link delete dev "${n}" || true
533533 '';
534534 });
535535···549549 path = [ pkgs.iproute2 ];
550550 script = ''
551551 # Remove Dead Interfaces
552552- ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
552552+ ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
553553 ip link add name "${n}" type ${v.type} \
554554 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
555555 ${optionalString (v.local != null) "local \"${v.local}\""} \
556556 ${optionalString (v.ttl != null) "${ttlarg} ${toString v.ttl}"} \
557557 ${optionalString (v.dev != null) "dev \"${v.dev}\""}
558558- ip link set "${n}" up
558558+ ip link set dev "${n}" up
559559 '';
560560 postStop = ''
561561- ip link delete "${n}" || true
561561+ ip link delete dev "${n}" || true
562562 '';
563563 });
564564···577577 path = [ pkgs.iproute2 ];
578578 script = ''
579579 # Remove Dead Interfaces
580580- ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
580580+ ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
581581 ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
582582583583 # We try to bring up the logical VLAN interface. If the master
584584 # interface the logical interface is dependent upon is not up yet we will
585585 # fail to immediately bring up the logical interface. The resulting logical
586586 # interface will brought up later when the master interface is up.
587587- ip link set "${n}" up || true
587587+ ip link set dev "${n}" up || true
588588 '';
589589 postStop = ''
590590- ip link delete "${n}" || true
590590+ ip link delete dev "${n}" || true
591591 '';
592592 });
593593
···442442 postStop = ''
443443 echo "Cleaning Open vSwitch ${n}"
444444 echo "Shutting down internal ${n} interface"
445445- ip link set ${n} down || true
445445+ ip link set dev ${n} down || true
446446 echo "Deleting flows for ${n}"
447447 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
448448 echo "Deleting Open vSwitch ${n}"
···1919 ];
20202121 postPatch = ''
2222- sed -e 's@"/sbin/ifconfig.*"@"${iproute2}/sbin/ip link set $IFNAME address $MAC mtu $MTU"@' -i src/device-linux.C
2222+ sed -e 's@"/sbin/ifconfig.*"@"${iproute2}/sbin/ip link set dev $IFNAME address $MAC mtu $MTU"@' -i src/device-linux.C
2323 sed -e 's@/sbin/ifconfig@${nettools}/sbin/ifconfig@g' -i src/device-*.C
2424 '';
2525
···339339 gr-rds = throw "'gr-rds' has been renamed to/replaced by 'gnuradio3_7.pkgs.rds'"; # Converted to throw 2023-09-10
340340 grub2_full = grub2; # Added 2022-11-18
341341 grub = throw "grub1 was removed after not being maintained upstream for a decade. Please switch to another bootloader"; # Added 2023-04-11
342342+ guile-disarchive = disarchive; # Added 2023-10-27
342343 guile-lint = throw "'guile-lint' has been removed, please use 'guild lint' instead"; # Added 2023-10-16
343344344345 ### H ###