github.com/openebs/node-disk-manager@v1.9.1-0.20230225014141-4531f06ffa1e/pkg/partition/partition.go (about)

     1  /*
     2  Copyright 2020 The OpenEBS Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package partition
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/diskfs/go-diskfs"
    23  	"github.com/diskfs/go-diskfs/disk"
    24  	"github.com/diskfs/go-diskfs/partition/gpt"
    25  	"github.com/openebs/node-disk-manager/pkg/blkid"
    26  
    27  	"k8s.io/klog/v2"
    28  )
    29  
    30  // TODO this package needs to be upstreamed to diskfs/go-diskfs
    31  
    32  const (
    33  	// BytesRequiredForGPTPartitionEntries is the total bytes required to store the GPT partition
    34  	// entries. 128 bytes are required per partition, total no of partition supported by GPT = 128
    35  	// Therefore, total bytes = 128*128
    36  	BytesRequiredForGPTPartitionEntries = 16384
    37  
    38  	// GPTPartitionStartByte is the byte on the disk at which the first partition starts.
    39  	// Normally partition starts at 1MiB, (as done by fdisk utility). This is done to
    40  	// align the partition start to physical block sizes on the disk.
    41  	GPTPartitionStartByte = 1048576
    42  
    43  	// NoOfLogicalBlocksForGPTHeader is the no. of logical blocks for the GPT header.
    44  	NoOfLogicalBlocksForGPTHeader = 1
    45  
    46  	// OpenEBSNDMPartitionName is the name meta info for openEBS created partitions.
    47  	OpenEBSNDMPartitionName = "OpenEBS_NDM"
    48  )
    49  
    50  // Disk struct represents a disk which needs to be partitioned
    51  type Disk struct {
    52  	// DevPath is the /dev/sdX entry of the disk
    53  	DevPath string
    54  	// DiskSize is size of disk in bytes
    55  	DiskSize uint64
    56  	// LogicalBlockSize is the block size of the disk normally 512 or 4k
    57  	LogicalBlockSize uint64
    58  
    59  	table *gpt.Table
    60  
    61  	disk *disk.Disk
    62  }
    63  
    64  // applyPartitionTable applies the partition table to the given disk.
    65  func (d *Disk) applyPartitionTable() error {
    66  	if len(d.table.Partitions) == 0 {
    67  		return fmt.Errorf("no partitions specified in partition table")
    68  	}
    69  
    70  	err := d.disk.Partition(d.table)
    71  	if err != nil {
    72  		return fmt.Errorf("unable to create/write partition table. %v", err)
    73  	}
    74  	return nil
    75  }
    76  
    77  // createPartitionTable creates gpt partition table structure with protective MBR turned on
    78  func (d *Disk) createPartitionTable() error {
    79  	if d.DiskSize == 0 {
    80  		klog.Errorf("disk %s has size zero", d.DevPath)
    81  		return fmt.Errorf("disk size is zero, unable to initialize partition table")
    82  	}
    83  	if d.LogicalBlockSize == 0 {
    84  		klog.Warningf("logical block size of %s not set, falling back to 512 bytes", d.DevPath)
    85  		klog.Warning("partitioning may fail.")
    86  		d.LogicalBlockSize = 512
    87  	}
    88  	// set protective MBR to true.
    89  	// https://en.wikipedia.org/wiki/GUID_Partition_Table#Protective_MBR_(LBA_0)
    90  	d.table = &gpt.Table{
    91  		LogicalSectorSize: int(d.LogicalBlockSize),
    92  		ProtectiveMBR:     true,
    93  	}
    94  	return nil
    95  }
    96  
    97  // addPartition is used to add a partition to the partition table.
    98  // Currently only a single partition can be created i.e, The method can be called only once for a disk.
    99  // TODO: @akhilerm, add method to create partition with given size
   100  func (d *Disk) addPartition() error {
   101  	var startSector, endSector uint64
   102  	if len(d.table.Partitions) == 0 {
   103  		// First sector of partition is aligned at 1MiB
   104  		startSector = (GPTPartitionStartByte) / d.LogicalBlockSize
   105  	}
   106  
   107  	PrimaryPartitionTableSize := BytesRequiredForGPTPartitionEntries/d.LogicalBlockSize + NoOfLogicalBlocksForGPTHeader
   108  
   109  	// last sector for the partition. Since GPT scheme contains a backup partition table at
   110  	// the last blocks of the disk.
   111  	endSector = (d.DiskSize / d.LogicalBlockSize) - PrimaryPartitionTableSize - 1
   112  
   113  	partition := &gpt.Partition{
   114  		Start: startSector,
   115  		End:   endSector,
   116  		Type:  gpt.LinuxFilesystem,
   117  		Name:  OpenEBSNDMPartitionName,
   118  	}
   119  	d.table.Partitions = append(d.table.Partitions, partition)
   120  	return nil
   121  }
   122  
   123  // CreateSinglePartition creates a single GPT partition on the disk
   124  // that spans the entire disk
   125  func (d *Disk) CreateSinglePartition() error {
   126  	fd, err := diskfs.Open(d.DevPath)
   127  	if err != nil {
   128  		return fmt.Errorf("error opening disk fd for disk %s: %v", d.DevPath, err)
   129  	}
   130  	d.disk = fd
   131  
   132  	// check for any existing partition table on the disk
   133  	if _, err := d.disk.GetPartitionTable(); err == nil {
   134  		klog.Errorf("aborting partition creation, disk %s already contains a known partition table", d.DevPath)
   135  		return fmt.Errorf("disk %s contains a partition table, cannot create a single partition", d.DevPath)
   136  	}
   137  
   138  	// check for any existing filesystem on the disk
   139  	deviceIdentifier := blkid.DeviceIdentifier{
   140  		DevPath: d.DevPath,
   141  	}
   142  	if fs := deviceIdentifier.GetOnDiskFileSystem(); len(fs) != 0 {
   143  		klog.Errorf("aborting partition creation, disk %s contains a known filesystem: %s", d.DevPath, fs)
   144  		return fmt.Errorf("disk %s contains a known filesyste: %s, cannot create a single partition", d.DevPath, fs)
   145  	}
   146  
   147  	err = d.createPartitionTable()
   148  	if err != nil {
   149  		klog.Error("partition table initialization failed")
   150  		return err
   151  	}
   152  
   153  	err = d.addPartition()
   154  	if err != nil {
   155  		klog.Error("could not add a partition to partition table")
   156  		return err
   157  	}
   158  
   159  	err = d.applyPartitionTable()
   160  	if err != nil {
   161  		klog.Error("writing partition table to disk failed")
   162  		return err
   163  	}
   164  	klog.Infof("created a single partition on disk %s", d.DevPath)
   165  	return nil
   166  }
   167  
   168  // CreatePartitionTable create a GPT header on the disk
   169  func (d *Disk) CreatePartitionTable() error {
   170  	fd, err := diskfs.Open(d.DevPath)
   171  	if err != nil {
   172  		return fmt.Errorf("error opening disk fd for disk %s: %v", d.DevPath, err)
   173  	}
   174  	d.disk = fd
   175  
   176  	// check for any existing partition table on the disk
   177  	if _, err := d.disk.GetPartitionTable(); err == nil {
   178  		klog.Errorf("aborting partition creation, disk %s already contains a known partition table", d.DevPath)
   179  		return fmt.Errorf("disk %s contains a partition table, cannot create a new partition table", d.DevPath)
   180  	}
   181  
   182  	// check for any existing filesystem on the disk
   183  	deviceIdentifier := blkid.DeviceIdentifier{
   184  		DevPath: d.DevPath,
   185  	}
   186  	if fs := deviceIdentifier.GetOnDiskFileSystem(); len(fs) != 0 {
   187  		klog.Errorf("aborting partition creation, disk %s contains a known filesystem: %s", d.DevPath, fs)
   188  		return fmt.Errorf("disk %s contains a known filesyste: %s, cannot create a partition table", d.DevPath, fs)
   189  	}
   190  
   191  	err = d.createPartitionTable()
   192  	if err != nil {
   193  		klog.Error("partition table initialization failed")
   194  		return err
   195  	}
   196  
   197  	err = d.disk.Partition(d.table)
   198  	if err != nil {
   199  		return fmt.Errorf("unable to create/write partition table. %v", err)
   200  	}
   201  	klog.Infof("created partition table on disk %s", d.DevPath)
   202  	return nil
   203  }