summaryrefslogblamecommitdiff
path: root/diskman/disk.cc
blob: 2d67b139ad76048c399cc608028b70c6c12e2767 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15














                                                                           

                              
                    
                    



                   





                                   














                                                                                    
                             










                                                    
         












                                                                                       

     





                                                                              

                                                                      
                                                



                                                                






                                                                          
                                                                                









                                                                                  
     

                   

                                 







                                                                                     
                                              








                                                                       
                                                 

                                                                          




                                                                                                   

                     
                                                










                                                                             






























                                                                          





                                                                                         



 
/*
 * disk.cc - Implementation of the Disk class
 * diskman, the Disk Manipulation library for
 * Project Horizon
 *
 * Copyright (c) 2020 Adélie Linux and contributors.  All rights reserved.
 * This code is licensed under the AGPL 3.0 license, as noted in the
 * LICENSE-code file in the root directory of this repository.
 *
 * SPDX-License-Identifier: AGPL-3.0-only
 */

#include "disk.hh"

#include <cstring>
#include <iostream>
#include <libfdisk/libfdisk.h>
#include <libudev.h>
#include <stdexcept>

namespace Horizon {
namespace DiskMan {

#define SAFE_SET(ivar, udev_call) \
    value = udev_call;\
    if(value != nullptr) {\
        ivar = std::string(value);\
    }

Disk::Disk(void *creation, int type, bool partition) {
    switch(type) {
    case 0: { /* udev */
        struct udev_device *device = static_cast<struct udev_device *>(creation);
        const char *value;

        SAFE_SET(_name, udev_device_get_sysname(device));
        SAFE_SET(_model, udev_device_get_property_value(device, "ID_MODEL"));
        SAFE_SET(_full_serial, udev_device_get_property_value(device, "ID_SERIAL"));
        SAFE_SET(_node, udev_device_get_devnode(device));
        SAFE_SET(_devpath, udev_device_get_devpath(device));

        value = udev_device_get_property_value(device, "ID_PART_TABLE_TYPE");
        if(value == nullptr) {
            _has_label = false;
            _label = Unknown;
        } else {
            _has_label = true;
            if(::strcmp(value, "apm") == 0) {
                _label = APM;
            } else if(::strcmp(value, "dos") == 0) {
                _label = MBR;
            } else if(::strcmp(value, "gpt") == 0) {
                _label = GPT;
            } else {
                _label = Unknown;
            }
        }

        value = udev_device_get_property_value(device, "ID_FS_TYPE");
        if(value == nullptr) {
            _has_fs = false;
        } else {
            _has_fs = true;
            _fs_type = std::string(value);
            SAFE_SET(_fs_label, udev_device_get_property_value(device, "ID_FS_LABEL"));
        }
        break;
    }
    default:
        throw new std::invalid_argument{ "invalid type code" };
    }

    total_mb = free_mb = contiguous_mb = 0;

    struct fdisk_context *ctxt = fdisk_new_context();
    if(ctxt != nullptr) {
        /* Open the device in read-only mode.  We don't need to write to it */
        if(fdisk_assign_device(ctxt, _node.c_str(), 1) == 0) {
            _sector = fdisk_get_sector_size(ctxt);
            total_mb = (fdisk_get_nsectors(ctxt) * _sector) / 1048576;
            struct fdisk_table *frees = nullptr;
            if(fdisk_has_label(ctxt) != 1) {
                /* Disk has no label, so consider it empty */
                free_mb = contiguous_mb = total_mb;
            } else if(fdisk_get_freespaces(ctxt, &frees) == 0) {
                for(size_t next = 0; next < fdisk_table_get_nents(frees);
                    next++) {
                    /* Each entry in frees is a "free space partition". */
                    struct fdisk_partition *part =
                            fdisk_table_get_partition(frees, next);
                    fdisk_sector_t size;
                    if(!fdisk_partition_has_size(part)) continue;
                    size = (fdisk_partition_get_size(part) * _sector) / 1048576;
                    free_mb += size;
                    if(size > contiguous_mb) contiguous_mb = size;
                }
                fdisk_unref_table(frees);
            }
            /* We used to free ctxt here, but it's useful for partition probing */
        } else {
            fdisk_unref_context(ctxt);
            ctxt = nullptr;
        }
    }

    if(partition) {
        if(reload_partitions()) {
            /* We're good */
        } else if(type == 0) {
            /* fallback to udev, if available */
            std::cerr << "Falling back to udev partition probing" << std::endl;

            struct udev_device *device = static_cast<struct udev_device *>(creation);
            struct udev *udev = udev_device_get_udev(device);
            struct udev_enumerate *part_enum = udev_enumerate_new(udev);
            if(part_enum != NULL) {
                struct udev_list_entry *first;

                udev_enumerate_add_match_subsystem(part_enum, "block");
                udev_enumerate_add_match_property(part_enum, "DEVTYPE",
                                                  "partition");
                udev_enumerate_add_match_parent(part_enum, device);
                udev_enumerate_scan_devices(part_enum);

                first = udev_enumerate_get_list_entry(part_enum);
                if(first != NULL) {
                    struct udev_list_entry *item;
                    udev_list_entry_foreach(item, first) {
                        const char *path = udev_list_entry_get_name(item);
                        struct udev_device *part_device = udev_device_new_from_syspath(udev, path);
                        if(part_device != nullptr) {
                            _partitions.push_back(Partition(*this, part_device, 1));
                            udev_device_unref(part_device);
                        }
                    }
                }
                udev_enumerate_unref(part_enum);
            }
        } else {
            std::cerr << "Cannot load partitions for " << _name << std::endl;
        }
    }

    if(ctxt != nullptr) {
        fdisk_unref_context(ctxt);
    }
}

bool Disk::reload_partitions() {
    bool success = false;
    struct fdisk_context *ctxt = fdisk_new_context();
    struct fdisk_table *parts = nullptr;

    if(ctxt == nullptr) {
        return false;
    }

    /* Open the device in read-only mode.  We don't need to write to it */
    if(fdisk_assign_device(ctxt, _node.c_str(), 1) != 0) {
        goto destroy_context;
    }

    if(fdisk_get_partitions(ctxt, &parts) == 0) {
        _partitions.clear();
        for(size_t next = 0; next < fdisk_table_get_nents(parts);
            next++) {
            struct fdisk_partition *part =
                    fdisk_table_get_partition(parts, next);
            _partitions.push_back(Partition(*this, part, 0));
        }
        success = true;
        fdisk_unref_table(parts);
    }

destroy_context:
    fdisk_unref_context(ctxt);
    return success;
}

const std::vector<Partition> Disk::partitions() const {
    if(!this->has_label()) {
        throw std::logic_error{ "attempt to retrieve partitions for non-labelled disk" };
    }

    return this->_partitions;
}

}
}