at 18.09-beta 22 kB view raw
1use strict; 2use warnings; 3use Class::Struct; 4use XML::LibXML; 5use File::Basename; 6use File::Path; 7use File::stat; 8use File::Copy; 9use File::Slurp; 10use File::Temp; 11require List::Compare; 12use POSIX; 13use Cwd; 14 15# system.build.toplevel path 16my $defaultConfig = $ARGV[1] or die; 17 18# Grub config XML generated by grubConfig function in grub.nix 19my $dom = XML::LibXML->load_xml(location => $ARGV[0]); 20 21sub get { my ($name) = @_; return $dom->findvalue("/expr/attrs/attr[\@name = '$name']/*/\@value"); } 22 23sub readFile { 24 my ($fn) = @_; local $/ = undef; 25 open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE; 26 local $/ = "\n"; chomp $s; return $s; 27} 28 29sub writeFile { 30 my ($fn, $s) = @_; 31 open FILE, ">$fn" or die "cannot create $fn: $!\n"; 32 print FILE $s or die; 33 close FILE or die; 34} 35 36sub runCommand { 37 my ($cmd) = @_; 38 open FILE, "$cmd 2>/dev/null |" or die "Failed to execute: $cmd\n"; 39 my @ret = <FILE>; 40 close FILE; 41 return ($?, @ret); 42} 43 44my $grub = get("grub"); 45my $grubVersion = int(get("version")); 46my $grubTarget = get("grubTarget"); 47my $extraConfig = get("extraConfig"); 48my $extraPrepareConfig = get("extraPrepareConfig"); 49my $extraPerEntryConfig = get("extraPerEntryConfig"); 50my $extraEntries = get("extraEntries"); 51my $extraEntriesBeforeNixOS = get("extraEntriesBeforeNixOS") eq "true"; 52my $extraInitrd = get("extraInitrd"); 53my $splashImage = get("splashImage"); 54my $splashMode = get("splashMode"); 55my $backgroundColor = get("backgroundColor"); 56my $configurationLimit = int(get("configurationLimit")); 57my $copyKernels = get("copyKernels") eq "true"; 58my $timeout = int(get("timeout")); 59my $defaultEntry = get("default"); 60my $fsIdentifier = get("fsIdentifier"); 61my $grubEfi = get("grubEfi"); 62my $grubTargetEfi = get("grubTargetEfi"); 63my $bootPath = get("bootPath"); 64my $storePath = get("storePath"); 65my $canTouchEfiVariables = get("canTouchEfiVariables"); 66my $efiInstallAsRemovable = get("efiInstallAsRemovable"); 67my $efiSysMountPoint = get("efiSysMountPoint"); 68my $gfxmodeEfi = get("gfxmodeEfi"); 69my $gfxmodeBios = get("gfxmodeBios"); 70my $bootloaderId = get("bootloaderId"); 71my $forceInstall = get("forceInstall"); 72my $font = get("font"); 73$ENV{'PATH'} = get("path"); 74 75die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2; 76 77print STDERR "updating GRUB $grubVersion menu...\n"; 78 79mkpath("$bootPath/grub", 0, 0700); 80 81# Discover whether the bootPath is on the same filesystem as / and 82# /nix/store. If not, then all kernels and initrds must be copied to 83# the bootPath. 84if (stat($bootPath)->dev != stat("/nix/store")->dev) { 85 $copyKernels = 1; 86} 87 88# Discover information about the location of the bootPath 89struct(Fs => { 90 device => '$', 91 type => '$', 92 mount => '$', 93}); 94sub PathInMount { 95 my ($path, $mount) = @_; 96 my @splitMount = split /\//, $mount; 97 my @splitPath = split /\//, $path; 98 if ($#splitPath < $#splitMount) { 99 return 0; 100 } 101 for (my $i = 0; $i <= $#splitMount; $i++) { 102 if ($splitMount[$i] ne $splitPath[$i]) { 103 return 0; 104 } 105 } 106 return 1; 107} 108 109# Figure out what filesystem is used for the directory with init/initrd/kernel files 110sub GetFs { 111 my ($dir) = @_; 112 my $bestFs = Fs->new(device => "", type => "", mount => ""); 113 foreach my $fs (read_file("/proc/self/mountinfo")) { 114 chomp $fs; 115 my @fields = split / /, $fs; 116 my $mountPoint = $fields[4]; 117 next unless -d $mountPoint; 118 my @mountOptions = split /,/, $fields[5]; 119 120 # Skip the optional fields. 121 my $n = 6; $n++ while $fields[$n] ne "-"; $n++; 122 my $fsType = $fields[$n]; 123 my $device = $fields[$n + 1]; 124 my @superOptions = split /,/, $fields[$n + 2]; 125 126 # Skip the bind-mount on /nix/store. 127 next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions); 128 # Skip mount point generated by systemd-efi-boot-generator? 129 next if $fsType eq "autofs"; 130 131 # Ensure this matches the intended directory 132 next unless PathInMount($dir, $mountPoint); 133 134 # Is it better than our current match? 135 if (length($mountPoint) > length($bestFs->mount)) { 136 $bestFs = Fs->new(device => $device, type => $fsType, mount => $mountPoint); 137 } 138 } 139 return $bestFs; 140} 141struct (Grub => { 142 path => '$', 143 search => '$', 144}); 145my $driveid = 1; 146sub GrubFs { 147 my ($dir) = @_; 148 my $fs = GetFs($dir); 149 my $path = substr($dir, length($fs->mount)); 150 if (substr($path, 0, 1) ne "/") { 151 $path = "/$path"; 152 } 153 my $search = ""; 154 155 if ($grubVersion > 1) { 156 # ZFS is completely separate logic as zpools are always identified by a label 157 # or custom UUID 158 if ($fs->type eq 'zfs') { 159 my $sid = index($fs->device, '/'); 160 161 if ($sid < 0) { 162 $search = '--label ' . $fs->device; 163 $path = '/@' . $path; 164 } else { 165 $search = '--label ' . substr($fs->device, 0, $sid); 166 $path = '/' . substr($fs->device, $sid) . '/@' . $path; 167 } 168 } else { 169 my %types = ('uuid' => '--fs-uuid', 'label' => '--label'); 170 171 if ($fsIdentifier eq 'provided') { 172 # If the provided dev is identifying the partition using a label or uuid, 173 # we should get the label / uuid and do a proper search 174 my @matches = $fs->device =~ m/\/dev\/disk\/by-(label|uuid)\/(.*)/; 175 if ($#matches > 1) { 176 die "Too many matched devices" 177 } elsif ($#matches == 1) { 178 $search = "$types{$matches[0]} $matches[1]" 179 } 180 } else { 181 # Determine the identifying type 182 $search = $types{$fsIdentifier} . ' '; 183 184 # Based on the type pull in the identifier from the system 185 my ($status, @devInfo) = runCommand("@utillinux@/bin/blkid -o export @{[$fs->device]}"); 186 if ($status != 0) { 187 die "Failed to get blkid info (returned $status) for @{[$fs->mount]} on @{[$fs->device]}"; 188 } 189 my @matches = join("", @devInfo) =~ m/@{[uc $fsIdentifier]}=([^\n]*)/; 190 if ($#matches != 0) { 191 die "Couldn't find a $types{$fsIdentifier} for @{[$fs->device]}\n" 192 } 193 $search .= $matches[0]; 194 } 195 196 # BTRFS is a special case in that we need to fix the referrenced path based on subvolumes 197 if ($fs->type eq 'btrfs') { 198 my ($status, @id_info) = runCommand("@btrfsprogs@/bin/btrfs subvol show @{[$fs->mount]}"); 199 if ($status != 0) { 200 die "Failed to retrieve subvolume info for @{[$fs->mount]}\n"; 201 } 202 my @ids = join("\n", @id_info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s; 203 if ($#ids > 0) { 204 die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n" 205 } elsif ($#ids == 0) { 206 my ($status, @path_info) = runCommand("@btrfsprogs@/bin/btrfs subvol list @{[$fs->mount]}"); 207 if ($status != 0) { 208 die "Failed to find @{[$fs->mount]} subvolume id from btrfs\n"; 209 } 210 my @paths = join("", @path_info) =~ m/ID $ids[0] [^\n]* path ([^\n]*)/; 211 if ($#paths > 0) { 212 die "Btrfs returned multiple paths for a single subvolume id, mountpoint @{[$fs->mount]}\n"; 213 } elsif ($#paths != 0) { 214 die "Btrfs did not return a path for the subvolume at @{[$fs->mount]}\n"; 215 } 216 $path = "/$paths[0]$path"; 217 } 218 } 219 } 220 if (not $search eq "") { 221 $search = "search --set=drive$driveid " . $search; 222 $path = "(\$drive$driveid)$path"; 223 $driveid += 1; 224 } 225 } 226 return Grub->new(path => $path, search => $search); 227} 228my $grubBoot = GrubFs($bootPath); 229my $grubStore; 230if ($copyKernels == 0) { 231 $grubStore = GrubFs($storePath); 232} 233my $extraInitrdPath; 234if ($extraInitrd) { 235 if (! -f $extraInitrd) { 236 print STDERR "Warning: the specified extraInitrd " . $extraInitrd . " doesn't exist. Your system won't boot without it.\n"; 237 } 238 $extraInitrdPath = GrubFs($extraInitrd); 239} 240 241# Generate the header. 242my $conf .= "# Automatically generated. DO NOT EDIT THIS FILE!\n"; 243 244if ($grubVersion == 1) { 245 $conf .= " 246 default $defaultEntry 247 timeout $timeout 248 "; 249 if ($splashImage) { 250 copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath\n"; 251 $conf .= "splashimage " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background.xpm.gz\n"; 252 } 253} 254 255else { 256 if ($copyKernels == 0) { 257 $conf .= " 258 " . $grubStore->search; 259 } 260 # FIXME: should use grub-mkconfig. 261 $conf .= " 262 " . $grubBoot->search . " 263 if [ -s \$prefix/grubenv ]; then 264 load_env 265 fi 266 267 # ‘grub-reboot’ sets a one-time saved entry, which we process here and 268 # then delete. 269 if [ \"\${next_entry}\" ]; then 270 set default=\"\${next_entry}\" 271 set next_entry= 272 save_env next_entry 273 set timeout=1 274 else 275 set default=$defaultEntry 276 set timeout=$timeout 277 fi 278 279 # Setup the graphics stack for bios and efi systems 280 if [ \"\${grub_platform}\" = \"efi\" ]; then 281 insmod efi_gop 282 insmod efi_uga 283 else 284 insmod vbe 285 fi 286 "; 287 288 if ($font) { 289 copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath\n"; 290 $conf .= " 291 insmod font 292 if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then 293 insmod gfxterm 294 if [ \"\${grub_platform}\" = \"efi\" ]; then 295 set gfxmode=$gfxmodeEfi 296 set gfxpayload=keep 297 else 298 set gfxmode=$gfxmodeBios 299 set gfxpayload=text 300 fi 301 terminal_output gfxterm 302 fi 303 "; 304 } 305 if ($splashImage) { 306 # Keeps the image's extension. 307 my ($filename, $dirs, $suffix) = fileparse($splashImage, qr"\..[^.]*$"); 308 # The module for jpg is jpeg. 309 if ($suffix eq ".jpg") { 310 $suffix = ".jpeg"; 311 } 312 if ($backgroundColor) { 313 $conf .= " 314 background_color '$backgroundColor' 315 "; 316 } 317 copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath\n"; 318 $conf .= " 319 insmod " . substr($suffix, 1) . " 320 if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then 321 set color_normal=white/black 322 set color_highlight=black/white 323 else 324 set menu_color_normal=cyan/blue 325 set menu_color_highlight=white/blue 326 fi 327 "; 328 } 329} 330 331$conf .= "$extraConfig\n"; 332 333 334# Generate the menu entries. 335$conf .= "\n"; 336 337my %copied; 338mkpath("$bootPath/kernels", 0, 0755) if $copyKernels; 339 340sub copyToKernelsDir { 341 my ($path) = @_; 342 return $grubStore->path . substr($path, length("/nix/store")) unless $copyKernels; 343 $path =~ /\/nix\/store\/(.*)/ or die; 344 my $name = $1; $name =~ s/\//-/g; 345 my $dst = "$bootPath/kernels/$name"; 346 # Don't copy the file if $dst already exists. This means that we 347 # have to create $dst atomically to prevent partially copied 348 # kernels or initrd if this script is ever interrupted. 349 if (! -e $dst) { 350 my $tmp = "$dst.tmp"; 351 copy $path, $tmp or die "cannot copy $path to $tmp\n"; 352 rename $tmp, $dst or die "cannot rename $tmp to $dst\n"; 353 } 354 $copied{$dst} = 1; 355 return ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/kernels/$name"; 356} 357 358sub addEntry { 359 my ($name, $path) = @_; 360 return unless -e "$path/kernel" && -e "$path/initrd"; 361 362 my $kernel = copyToKernelsDir(Cwd::abs_path("$path/kernel")); 363 my $initrd = copyToKernelsDir(Cwd::abs_path("$path/initrd")); 364 if ($extraInitrd) { 365 $initrd .= " " .$extraInitrdPath->path; 366 } 367 my $xen = -e "$path/xen.gz" ? copyToKernelsDir(Cwd::abs_path("$path/xen.gz")) : undef; 368 369 # FIXME: $confName 370 371 my $kernelParams = 372 "systemConfig=" . Cwd::abs_path($path) . " " . 373 "init=" . Cwd::abs_path("$path/init") . " " . 374 readFile("$path/kernel-params"); 375 my $xenParams = $xen && -e "$path/xen-params" ? readFile("$path/xen-params") : ""; 376 377 if ($grubVersion == 1) { 378 $conf .= "title $name\n"; 379 $conf .= " $extraPerEntryConfig\n" if $extraPerEntryConfig; 380 $conf .= " kernel $xen $xenParams\n" if $xen; 381 $conf .= " " . ($xen ? "module" : "kernel") . " $kernel $kernelParams\n"; 382 $conf .= " " . ($xen ? "module" : "initrd") . " $initrd\n\n"; 383 } else { 384 $conf .= "menuentry \"$name\" {\n"; 385 $conf .= $grubBoot->search . "\n"; 386 if ($copyKernels == 0) { 387 $conf .= $grubStore->search . "\n"; 388 } 389 if ($extraInitrd) { 390 $conf .= $extraInitrdPath->search . "\n"; 391 } 392 $conf .= " $extraPerEntryConfig\n" if $extraPerEntryConfig; 393 $conf .= " multiboot $xen $xenParams\n" if $xen; 394 $conf .= " " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n"; 395 $conf .= " " . ($xen ? "module" : "initrd") . " $initrd\n"; 396 $conf .= "}\n\n"; 397 } 398} 399 400 401# Add default entries. 402$conf .= "$extraEntries\n" if $extraEntriesBeforeNixOS; 403 404addEntry("NixOS - Default", $defaultConfig); 405 406$conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS; 407 408my $grubBootPath = $grubBoot->path; 409# extraEntries could refer to @bootRoot@, which we have to substitute 410$conf =~ s/\@bootRoot\@/$grubBootPath/g; 411 412# Emit submenus for all system profiles. 413sub addProfile { 414 my ($profile, $description) = @_; 415 416 # Add entries for all generations of this profile. 417 $conf .= "submenu \"$description\" {\n" if $grubVersion == 2; 418 419 sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; } 420 421 my @links = sort 422 { nrFromGen($b) <=> nrFromGen($a) } 423 (glob "$profile-*-link"); 424 425 my $curEntry = 0; 426 foreach my $link (@links) { 427 last if $curEntry++ >= $configurationLimit; 428 if (! -e "$link/nixos-version") { 429 warn "skipping corrupt system profile entry ‘$link’\n"; 430 next; 431 } 432 my $date = strftime("%F", localtime(lstat($link)->mtime)); 433 my $version = 434 -e "$link/nixos-version" 435 ? readFile("$link/nixos-version") 436 : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]); 437 addEntry("NixOS - Configuration " . nrFromGen($link) . " ($date - $version)", $link); 438 } 439 440 $conf .= "}\n" if $grubVersion == 2; 441} 442 443addProfile "/nix/var/nix/profiles/system", "NixOS - All configurations"; 444 445if ($grubVersion == 2) { 446 for my $profile (glob "/nix/var/nix/profiles/system-profiles/*") { 447 my $name = basename($profile); 448 next unless $name =~ /^\w+$/; 449 addProfile $profile, "NixOS - Profile '$name'"; 450 } 451} 452 453# Run extraPrepareConfig in sh 454if ($extraPrepareConfig ne "") { 455 system((get("shell"), "-c", $extraPrepareConfig)); 456} 457 458# write the GRUB config. 459my $confFile = $grubVersion == 1 ? "$bootPath/grub/menu.lst" : "$bootPath/grub/grub.cfg"; 460my $tmpFile = $confFile . ".tmp"; 461writeFile($tmpFile, $conf); 462 463 464# check whether to install GRUB EFI or not 465sub getEfiTarget { 466 if ($grubVersion == 1) { 467 return "no" 468 } elsif (($grub ne "") && ($grubEfi ne "")) { 469 # EFI can only be installed when target is set; 470 # A target is also required then for non-EFI grub 471 if (($grubTarget eq "") || ($grubTargetEfi eq "")) { die } 472 else { return "both" } 473 } elsif (($grub ne "") && ($grubEfi eq "")) { 474 # TODO: It would be safer to disallow non-EFI grub installation if no taget is given. 475 # If no target is given, then grub auto-detects the target which can lead to errors. 476 # E.g. it seems as if grub would auto-detect a EFI target based on the availability 477 # of a EFI partition. 478 # However, it seems as auto-detection is currently relied on for non-x86_64 and non-i386 479 # architectures in NixOS. That would have to be fixed in the nixos modules first. 480 return "no" 481 } elsif (($grub eq "") && ($grubEfi ne "")) { 482 # EFI can only be installed when target is set; 483 if ($grubTargetEfi eq "") { die } 484 else {return "only" } 485 } else { 486 # prevent an installation if neither grub nor grubEfi is given 487 return "neither" 488 } 489} 490 491my $efiTarget = getEfiTarget(); 492 493# Append entries detected by os-prober 494if (get("useOSProber") eq "true") { 495 my $targetpackage = ($efiTarget eq "no") ? $grub : $grubEfi; 496 system(get("shell"), "-c", "pkgdatadir=$targetpackage/share/grub $targetpackage/etc/grub.d/30_os-prober >> $tmpFile"); 497} 498 499# Atomically switch to the new config 500rename $tmpFile, $confFile or die "cannot rename $tmpFile to $confFile\n"; 501 502 503# Remove obsolete files from $bootPath/kernels. 504foreach my $fn (glob "$bootPath/kernels/*") { 505 next if defined $copied{$fn}; 506 print STDERR "removing obsolete file $fn\n"; 507 unlink $fn; 508} 509 510 511# 512# Install GRUB if the parameters changed from the last time we installed it. 513# 514 515struct(GrubState => { 516 name => '$', 517 version => '$', 518 efi => '$', 519 devices => '$', 520 efiMountPoint => '$', 521}); 522sub readGrubState { 523 my $defaultGrubState = GrubState->new(name => "", version => "", efi => "", devices => "", efiMountPoint => "" ); 524 open FILE, "<$bootPath/grub/state" or return $defaultGrubState; 525 local $/ = "\n"; 526 my $name = <FILE>; 527 chomp($name); 528 my $version = <FILE>; 529 chomp($version); 530 my $efi = <FILE>; 531 chomp($efi); 532 my $devices = <FILE>; 533 chomp($devices); 534 my $efiMountPoint = <FILE>; 535 chomp($efiMountPoint); 536 close FILE; 537 my $grubState = GrubState->new(name => $name, version => $version, efi => $efi, devices => $devices, efiMountPoint => $efiMountPoint ); 538 return $grubState 539} 540 541sub getDeviceTargets { 542 my @devices = (); 543 foreach my $dev ($dom->findnodes('/expr/attrs/attr[@name = "devices"]/list/string/@value')) { 544 $dev = $dev->findvalue(".") or die; 545 push(@devices, $dev); 546 } 547 return @devices; 548} 549my @deviceTargets = getDeviceTargets(); 550my $prevGrubState = readGrubState(); 551my @prevDeviceTargets = split/,/, $prevGrubState->devices; 552 553my $devicesDiffer = scalar (List::Compare->new( '-u', '-a', \@deviceTargets, \@prevDeviceTargets)->get_symmetric_difference()); 554my $nameDiffer = get("fullName") ne $prevGrubState->name; 555my $versionDiffer = get("fullVersion") ne $prevGrubState->version; 556my $efiDiffer = $efiTarget ne $prevGrubState->efi; 557my $efiMountPointDiffer = $efiSysMountPoint ne $prevGrubState->efiMountPoint; 558if (($ENV{'NIXOS_INSTALL_GRUB'} // "") eq "1") { 559 warn "NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER"; 560 $ENV{'NIXOS_INSTALL_BOOTLOADER'} = "1"; 561} 562my $requireNewInstall = $devicesDiffer || $nameDiffer || $versionDiffer || $efiDiffer || $efiMountPointDiffer || (($ENV{'NIXOS_INSTALL_BOOTLOADER'} // "") eq "1"); 563 564# install a symlink so that grub can detect the boot drive 565my $tmpDir = File::Temp::tempdir(CLEANUP => 1) or die "Failed to create temporary space"; 566symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot"; 567 568# install non-EFI GRUB 569if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) { 570 foreach my $dev (@deviceTargets) { 571 next if $dev eq "nodev"; 572 print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n"; 573 my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev)); 574 if ($forceInstall eq "true") { 575 push @command, "--force"; 576 } 577 if ($grubTarget ne "") { 578 push @command, "--target=$grubTarget"; 579 } 580 (system @command) == 0 or die "$0: installation of GRUB on $dev failed\n"; 581 } 582} 583 584 585# install EFI GRUB 586if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) { 587 print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n"; 588 my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint"); 589 if ($forceInstall eq "true") { 590 push @command, "--force"; 591 } 592 if ($canTouchEfiVariables eq "true") { 593 push @command, "--bootloader-id=$bootloaderId"; 594 } else { 595 push @command, "--no-nvram"; 596 push @command, "--removable" if $efiInstallAsRemovable eq "true"; 597 } 598 599 (system @command) == 0 or die "$0: installation of GRUB EFI into $efiSysMountPoint failed\n"; 600} 601 602 603# update GRUB state file 604if ($requireNewInstall != 0) { 605 open FILE, ">$bootPath/grub/state" or die "cannot create $bootPath/grub/state: $!\n"; 606 print FILE get("fullName"), "\n" or die; 607 print FILE get("fullVersion"), "\n" or die; 608 print FILE $efiTarget, "\n" or die; 609 print FILE join( ",", @deviceTargets ), "\n" or die; 610 print FILE $efiSysMountPoint, "\n" or die; 611 close FILE or die; 612}