github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/gadget/install/partition.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package install
    21  
    22  import (
    23  	"fmt"
    24  	"os/exec"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/snapcore/snapd/gadget"
    30  	"github.com/snapcore/snapd/logger"
    31  	"github.com/snapcore/snapd/osutil"
    32  )
    33  
    34  var (
    35  	ensureNodesExist = ensureNodesExistImpl
    36  )
    37  
    38  // createMissingPartitions creates the partitions listed in the laid out volume
    39  // pv that are missing from the existing device layout, returning a list of
    40  // structures that have been created.
    41  func createMissingPartitions(dl *gadget.OnDiskVolume, pv *gadget.LaidOutVolume) ([]gadget.OnDiskStructure, error) {
    42  	buf, created := gadget.BuildPartitionList(dl, pv)
    43  	if len(created) == 0 {
    44  		return created, nil
    45  	}
    46  
    47  	logger.Debugf("create partitions on %s: %s", dl.Device, buf.String())
    48  
    49  	// Write the partition table. By default sfdisk will try to re-read the
    50  	// partition table with the BLKRRPART ioctl but will fail because the
    51  	// kernel side rescan removes and adds partitions and we have partitions
    52  	// mounted (so it fails on removal). Use --no-reread to skip this attempt.
    53  	cmd := exec.Command("sfdisk", "--append", "--no-reread", dl.Device)
    54  	cmd.Stdin = buf
    55  	if output, err := cmd.CombinedOutput(); err != nil {
    56  		return created, osutil.OutputErr(output, err)
    57  	}
    58  
    59  	// Re-read the partition table
    60  	if err := reloadPartitionTable(dl.Device); err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	// Make sure the devices for the partitions we created are available
    65  	if err := ensureNodesExist(created, 5*time.Second); err != nil {
    66  		return nil, fmt.Errorf("partition not available: %v", err)
    67  	}
    68  
    69  	return created, nil
    70  }
    71  
    72  // removeCreatedPartitions removes partitions added during a previous install.
    73  func removeCreatedPartitions(dl *gadget.OnDiskVolume) error {
    74  	indexes := make([]string, 0, len(dl.Structure))
    75  	for i, s := range dl.Structure {
    76  		if s.CreatedDuringInstall {
    77  			logger.Noticef("partition %s was created during previous install", s.Node)
    78  			indexes = append(indexes, strconv.Itoa(i+1))
    79  		}
    80  	}
    81  	if len(indexes) == 0 {
    82  		return nil
    83  	}
    84  
    85  	// Delete disk partitions
    86  	logger.Debugf("delete disk partitions %v", indexes)
    87  	cmd := exec.Command("sfdisk", append([]string{"--no-reread", "--delete", dl.Device}, indexes...)...)
    88  	if output, err := cmd.CombinedOutput(); err != nil {
    89  		return osutil.OutputErr(output, err)
    90  	}
    91  
    92  	// Reload the partition table
    93  	if err := reloadPartitionTable(dl.Device); err != nil {
    94  		return err
    95  	}
    96  
    97  	// Re-read the partition table from the device to update our partition list
    98  	if err := gadget.UpdatePartitionList(dl); err != nil {
    99  		return err
   100  	}
   101  
   102  	// Ensure all created partitions were removed
   103  	if remaining := gadget.CreatedDuringInstall(dl); len(remaining) > 0 {
   104  		return fmt.Errorf("cannot remove partitions: %s", strings.Join(remaining, ", "))
   105  	}
   106  
   107  	return nil
   108  }
   109  
   110  // ensureNodeExists makes sure the device nodes for all device structures are
   111  // available and notified to udev, within a specified amount of time.
   112  func ensureNodesExistImpl(dss []gadget.OnDiskStructure, timeout time.Duration) error {
   113  	t0 := time.Now()
   114  	for _, ds := range dss {
   115  		found := false
   116  		for time.Since(t0) < timeout {
   117  			if osutil.FileExists(ds.Node) {
   118  				found = true
   119  				break
   120  			}
   121  			time.Sleep(100 * time.Millisecond)
   122  		}
   123  		if found {
   124  			if err := udevTrigger(ds.Node); err != nil {
   125  				return err
   126  			}
   127  		} else {
   128  			return fmt.Errorf("device %s not available", ds.Node)
   129  		}
   130  	}
   131  	return nil
   132  }
   133  
   134  // reloadPartitionTable instructs the kernel to re-read the partition
   135  // table of a given block device.
   136  func reloadPartitionTable(device string) error {
   137  	// Re-read the partition table using the BLKPG ioctl, which doesn't
   138  	// remove existing partitions, only appends new partitions with the right
   139  	// size and offset. As long as we provide consistent partitioning from
   140  	// userspace we're safe.
   141  	output, err := exec.Command("partx", "-u", device).CombinedOutput()
   142  	if err != nil {
   143  		return osutil.OutputErr(output, err)
   144  	}
   145  	return nil
   146  }
   147  
   148  // udevTrigger triggers udev for the specified device and waits until
   149  // all events in the udev queue are handled.
   150  func udevTrigger(device string) error {
   151  	if output, err := exec.Command("udevadm", "trigger", "--settle", device).CombinedOutput(); err != nil {
   152  		return osutil.OutputErr(output, err)
   153  	}
   154  	return nil
   155  }