├── .drone.yml ├── .github ├── FUNDING.yml └── workflows │ └── ubuntu_test.yml ├── .gitignore ├── CMakeLists.txt ├── COPYING ├── README.md ├── doc ├── growlight-1.2.8.png ├── growlight.jpg └── man │ └── man8 │ ├── growlight-readline.8.md │ └── growlight.8.md ├── src ├── aggregate.c ├── aggregate.h ├── apm.c ├── apm.h ├── crypt.c ├── crypt.h ├── dm.c ├── dm.h ├── dmi.c ├── dmi.h ├── fs.c ├── fs.h ├── gpt.c ├── gpt.h ├── growlight.c ├── growlight.h ├── health.c ├── health.h ├── libblkid.c ├── libblkid.h ├── mbr.c ├── mbr.h ├── mdadm.c ├── mdadm.h ├── mmap.c ├── mmap.h ├── mounts.c ├── mounts.h ├── msdos.c ├── msdos.h ├── notcurses │ ├── notcurses-ui.h │ ├── notcurses.c │ ├── notui-aggregate.c │ └── notui-aggregate.h ├── nvme.c ├── nvme.h ├── popen.c ├── popen.h ├── ptable.c ├── ptable.h ├── ptypes.c ├── ptypes.h ├── readline │ └── readline.c ├── secure.c ├── secure.h ├── sg.c ├── sg.h ├── sha.c ├── sha.h ├── smart.c ├── smart.h ├── ssd.c ├── ssd.h ├── stats.c ├── stats.h ├── swap.c ├── swap.h ├── sysfs.c ├── sysfs.h ├── target.c ├── target.h ├── threads.c ├── threads.h ├── udev.c ├── udev.h ├── version.h.in ├── zfs.c └── zfs.h ├── tests ├── gpt.cpp ├── main.cpp └── main.h └── tools ├── debrelease └── release /.drone.yml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: pipeline 3 | type: docker 4 | name: debian-unstable 5 | 6 | steps: 7 | - name: debian-build 8 | image: dankamongmen/unstable_builder:2022-01-29a 9 | commands: 10 | - export LANG=en_US.UTF-8 11 | - export TERM=xterm 12 | - mkdir build 13 | - cd build 14 | - cmake -DUSE_LIBZFS=off .. 15 | - make 16 | - ctest -v --output-on-failure 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: dankamongmen 4 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu_test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ubuntu 3 | 4 | on: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | tests: 12 | env: 13 | COLORTERM: truecolor 14 | NPROC: 2 15 | TERM: xterm 16 | name: 🐧 build, test, & install 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | 21 | - name: Install tools and libraries via APT 22 | run: | 23 | sudo apt update 24 | sudo apt install -y \ 25 | build-essential \ 26 | cmake \ 27 | nettle-dev \ 28 | libatasmart-dev \ 29 | libnotcurses-dev \ 30 | libcap-dev \ 31 | libcryptsetup-dev \ 32 | libblkid-dev \ 33 | libdevmapper-dev \ 34 | nettle-dev \ 35 | libudev-dev \ 36 | libpci-dev \ 37 | libpciaccess-dev \ 38 | doctest-dev \ 39 | pandoc 40 | 41 | - uses: actions/checkout@v2 42 | 43 | - name: cmake 44 | run: | 45 | mkdir build && cd build 46 | cmake .. \ 47 | -DCMAKE_BUILD_TYPE=Release -DUSE_LIBZFS=off 48 | 49 | - name: make 50 | run: | 51 | cd build 52 | make -j${NPROC} 53 | 54 | - name: ctest 55 | run: | 56 | cd build 57 | ctest --output-on-failure 58 | 59 | - name: make install 60 | run: | 61 | cd build 62 | sudo make install 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | aclocal.m4 3 | autom4te.cache/ 4 | autoscan.log 5 | compile 6 | config.guess* 7 | config.log* 8 | config.status* 9 | config.sub* 10 | configure 11 | configure.scan 12 | depcomp 13 | .deps/ 14 | doc/growlight.ent 15 | growlight 16 | growlight-curses 17 | growlight-readline 18 | growlight-test 19 | growlight.8 20 | growlight.xhtml 21 | growlight-curses.8 22 | growlight-curses.xhtml 23 | growlight-readline.8 24 | growlight-readline.xhtml 25 | growlight-*.tar.bz2 26 | growlight-*.tar.gz 27 | install-sh 28 | Makefile 29 | Makefile.in 30 | missing 31 | .pc 32 | src/version.h 33 | tags 34 | .tags 35 | test-driver 36 | growlight-test.log 37 | growlight-test.trs 38 | test-suite.log 39 | */.dirstamp 40 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(growlight VERSION 1.2.38 3 | DESCRIPTION "Block device and filesystem manager" 4 | HOMEPAGE_URL "https://nick-black.com/dankwiki/index.php/Growlight" 5 | LANGUAGES C CXX) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | set(CMAKE_C_STANDARD 11) 8 | set(CMAKE_C_VISIBILITY_PRESET hidden) 9 | set(CMAKE_CXX_STANDARD 17) 10 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 11 | 12 | include(CTest) 13 | include(GNUInstallDirs) 14 | include(FeatureSummary) 15 | include(CMakeDependentOption) 16 | 17 | ###################### USER-SELECTABLE OPTIONS ########################### 18 | # BUILD_TESTING is defined by CTest 19 | cmake_dependent_option( 20 | USE_DOCTEST "Use doctest to build unit tests" ON 21 | "${BUILD_TESTING}" OFF 22 | ) 23 | option(USE_PANDOC "Use pandoc to write man pages" ON) 24 | option(USE_LIBZFS "Use libzfs to manage zpools/ZFS" ON) 25 | #################### END USER-SELECTABLE OPTIONS ######################### 26 | 27 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 28 | set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the build mode." FORCE) 29 | endif() 30 | 31 | add_compile_definitions(_FORTIFY_SOURCE=2) 32 | add_compile_options(-Wall -Wextra -W -Wshadow -Wformat -fexceptions) 33 | 34 | find_package(PkgConfig REQUIRED) 35 | find_package(Threads) 36 | set_package_properties(Threads PROPERTIES TYPE REQUIRED) 37 | find_package(Notcurses 3.0.5 CONFIG) 38 | set_package_properties(Notcurses PROPERTIES TYPE REQUIRED) 39 | pkg_check_modules(LIBATASMART REQUIRED libatasmart>=0.19) 40 | pkg_check_modules(LIBBLKID REQUIRED blkid>=2.20.1) 41 | pkg_check_modules(LIBCAP REQUIRED libcap>=2.24) 42 | pkg_check_modules(LIBCRYPTSETUP REQUIRED libcryptsetup>=2.0.2) 43 | pkg_check_modules(LIBDEVMAPPER REQUIRED devmapper>=1.02.74) 44 | pkg_check_modules(LIBNETTLE REQUIRED nettle>=3.5.1) 45 | pkg_check_modules(LIBPCI REQUIRED libpci>=3.1.9) 46 | pkg_check_modules(LIBPCIACCESS REQUIRED pciaccess>=0.13.1) 47 | pkg_check_modules(LIBUDEV REQUIRED libudev>=175) 48 | pkg_check_modules(LIBZ REQUIRED zlib>=1.2.11) 49 | if(${USE_DOCTEST}) 50 | find_package(doctest 2.3.5) 51 | set_package_properties(doctest PROPERTIES TYPE REQUIRED) 52 | endif() 53 | if(${USE_LIBZFS}) 54 | pkg_check_modules(LIBZFS REQUIRED libzfs>=0.8) 55 | endif() 56 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 57 | 58 | include(CMakePackageConfigHelpers) 59 | configure_file(src/version.h.in include/version.h) 60 | 61 | set(COMMON_INCLUDE_DIRS 62 | "${LIBATASMART_INCLUDE_DIRS}" 63 | "${LIBBLKID_INCLUDE_DIRS}" 64 | "${LIBCAP_INCLUDE_DIRS}" 65 | "${LIBCRYPTSETUP_INCLUDE_DIRS}" 66 | "${LIBDEVMAPPER_INCLUDE_DIRS}" 67 | "${LIBNETTLE_INCLUDE_DIRS}" 68 | "${LIBPCI_INCLUDE_DIRS}" 69 | "${LIBPCIACCESS_INCLUDE_DIRS}" 70 | "${LIBUDEV_INCLUDE_DIRS}" 71 | "${LIBZ_INCLUDE_DIRS}" 72 | "${LIBZFS_INCLUDE_DIRS}" 73 | "${Notcurses_INCLUDE_DIRS}" 74 | ) 75 | 76 | set(COMMON_LIBRARY_DIRS 77 | "${LIBATASMART_LIBRARY_DIRS}" 78 | "${LIBBLKID_LIBRARY_DIRS}" 79 | "${LIBCAP_LIBRARY_DIRS}" 80 | "${LIBCRYPTSETUP_LIBRARY_DIRS}" 81 | "${LIBDEVMAPPER_LIBRARY_DIRS}" 82 | "${LIBNETTLE_LIBRARY_DIRS}" 83 | "${LIBPCI_LIBRARY_DIRS}" 84 | "${LIBPCIACCESS_LIBRARY_DIRS}" 85 | "${LIBUDEV_LIBRARY_DIRS}" 86 | "${LIBZ_LIBRARY_DIRS}" 87 | "${LIBZFS_LIBRARY_DIRS}" 88 | "${Notcurses_LIBRARY_DIRS}" 89 | ) 90 | 91 | set(COMMON_LIBRARIES 92 | "${LIBATASMART_LIBRARIES}" 93 | "${LIBBLKID_LIBRARIES}" 94 | "${LIBCAP_LIBRARIES}" 95 | "${LIBCRYPTSETUP_LIBRARIES}" 96 | "${LIBDEVMAPPER_LIBRARIES}" 97 | "${LIBNETTLE_LIBRARIES}" 98 | "${LIBPCI_LIBRARIES}" 99 | "${LIBPCIACCESS_LIBRARIES}" 100 | "${LIBUDEV_LIBRARIES}" 101 | "${LIBZ_LIBRARIES}" 102 | "${LIBZFS_LIBRARIES}" 103 | "${Notcurses_LIBRARIES}" 104 | Threads::Threads 105 | ) 106 | 107 | file(GLOB SRCS CONFIGURE_DEPENDS src/*.c) 108 | file(GLOB NCSRCS CONFIGURE_DEPENDS src/notcurses/*.c) 109 | add_executable(growlight ${SRCS} ${NCSRCS}) 110 | target_include_directories(growlight 111 | PRIVATE 112 | src 113 | "${PROJECT_BINARY_DIR}/include" 114 | "${COMMON_INCLUDE_DIRS}" 115 | ) 116 | target_compile_definitions(growlight 117 | PRIVATE 118 | _GNU_SOURCE _DEFAULT_SOURCE _XOPEN_SOURCE=600 119 | ) 120 | target_link_libraries(growlight 121 | PRIVATE 122 | "${COMMON_LIBRARIES}" 123 | ) 124 | target_link_directories(growlight 125 | PRIVATE 126 | "${COMMON_LIBRARY_DIRS}" 127 | ) 128 | file(GLOB RLSRCS CONFIGURE_DEPENDS src/readline/*.c) 129 | add_executable(growlight-readline ${SRCS} ${RLSRCS}) 130 | target_include_directories(growlight-readline 131 | PRIVATE 132 | src 133 | "${PROJECT_BINARY_DIR}/include" 134 | "${COMMON_INCLUDE_DIRS}" 135 | ) 136 | target_compile_definitions(growlight-readline 137 | PRIVATE 138 | _GNU_SOURCE _DEFAULT_SOURCE _XOPEN_SOURCE=600 139 | ) 140 | target_link_libraries(growlight-readline 141 | PRIVATE 142 | "${COMMON_LIBRARIES}" 143 | ) 144 | target_link_directories(growlight-readline 145 | PRIVATE 146 | "${COMMON_LIBRARY_DIRS}" 147 | ) 148 | 149 | if(${USE_DOCTEST}) 150 | file(GLOB TESTSRCS CONFIGURE_DEPENDS tests/*.cpp) 151 | add_executable(growlight-tester ${SRCS} ${TESTSRCS}) 152 | target_include_directories(growlight-tester 153 | PRIVATE 154 | src 155 | "${PROJECT_BINARY_DIR}/include" 156 | "${COMMON_INCLUDE_DIRS}" 157 | ) 158 | target_compile_definitions(growlight-tester 159 | PRIVATE 160 | _GNU_SOURCE _DEFAULT_SOURCE _XOPEN_SOURCE=600 161 | ) 162 | target_link_libraries(growlight-tester 163 | PRIVATE 164 | "${COMMON_LIBRARIES}" 165 | ) 166 | target_link_directories(growlight-tester 167 | PRIVATE 168 | "${COMMON_LIBRARY_DIRS}" 169 | ) 170 | add_test( 171 | NAME growlight-tester 172 | COMMAND growlight-tester 173 | ) 174 | endif() 175 | 176 | add_test( 177 | NAME blockdev-verbose 178 | COMMAND sh -c "echo 'blockdev -v' | ./growlight-readline -v --notroot" 179 | ) 180 | 181 | # Pandoc documentation (man pages) 182 | if(USE_PANDOC) 183 | file(GLOB MANSOURCE8 CONFIGURE_DEPENDS doc/man/man8/*.md) 184 | find_program(PANDOC pandoc) 185 | if(NOT PANDOC) 186 | message(FATAL_ERROR "pandoc not found. USE_PANDOC=OFF to disable.") 187 | else() 188 | foreach(m ${MANSOURCE8}) 189 | get_filename_component(me ${m} NAME_WLE) 190 | add_custom_command( 191 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${me} 192 | DEPENDS ${m} 193 | COMMAND ${PANDOC} 194 | ARGS --to man --standalone --from markdown-smart ${m} > ${CMAKE_CURRENT_BINARY_DIR}/${me} 195 | COMMENT "Building man page ${me}" 196 | ) 197 | add_custom_target(${me}.man 198 | ALL 199 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${me} 200 | ) 201 | endforeach() 202 | foreach(m ${MANSOURCE8}) 203 | get_filename_component(me ${m} NAME_WLE) 204 | LIST(APPEND MANPAGES8 ${CMAKE_CURRENT_BINARY_DIR}/${me}) 205 | endforeach() 206 | endif() 207 | endif() 208 | 209 | install(FILES doc/growlight.jpg DESTINATION ${CMAKE_INSTALL_FULL_DATADIR}/growlight) 210 | install(TARGETS growlight DESTINATION sbin) 211 | install(TARGETS growlight-readline DESTINATION sbin) 212 | install(FILES ${MANPAGES8} DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man8) 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # growlight by nick black (nickblack@linux.com) 2 | 3 | Block device manager and system installation tool. 4 | 5 | https://nick-black.com/dankwiki/index.php/Growlight 6 | 7 |

8 | 9 |

10 | 11 | [![Build Status](https://drone.dsscaw.com:4443/api/badges/dankamongmen/growlight/status.svg)](https://drone.dsscaw.com:4443/dankamongmen/growlight) 12 | 13 | 14 | Packaging status 15 | 16 | 17 | Dependencies: 18 | 19 | - libatasmart 0.19+ 20 | - libblkid 2.20.1 21 | - libcap 2.24+ 22 | - libcryptsetup 2.1.5+ 23 | - libdevmapper 1.02.74+ 24 | - libnettle 3.5.1+ 25 | - libnotcurses 3.0.5+ 26 | - libpci 3.1.9+ 27 | - libpciaccess 0.13.1+ 28 | - libudev 175+ 29 | - libz 1.2.11+ 30 | - mkswap(8) from util-linux 31 | - badblocks(8), mkfs.ext4(8), mkfs.ext3(8), mkfs.ext2(8) from e2fsprogs 32 | 33 | Kernel options: 34 | 35 | - CONFIG_DM_CRYPT (for device mapper encrypt aka LUKS) 36 | - CONFIG_MD_RAID* (for MDRAID) 37 | - CONFIG_MSDOS_PARTITION (for msdos partition tables) 38 | - CONFIG_EFI_PARTITION (for GPT partition tables) 39 | ... almost certainly more 40 | 41 | Build-only dependencies: 42 | 43 | - pkg-config 0.29+ 44 | - cmake 3.14+ 45 | - pandoc 2.9.2.1+ (if building man pages) 46 | - doctest 2.3.5+ (if building unit tests) 47 | 48 | Building: 49 | 50 | - mkdir build && cd build 51 | - cmake .. 52 | - make 53 | - (optionally) make check 54 | 55 | ### User's guide 56 | 57 | In almost all cases, growlight needs to be run as root. It will attempt to 58 | start otherwise, but will generally be unable to discover or manipulate disks. 59 | You'll definitely need at least `CAP_SYS_RAWIO` and `CAP_SYS_ADMIN`. 60 | 61 | Help can be found by pressing 'H' or 'F1' in `growlight`, or running `help` 62 | in `growlight-readline`. 63 | 64 | growlight's first action is to install inotify watches in several directories, 65 | and then enumerate the current devices by walking same (`/sys/class/block`, 66 | etc.). This way, it immediately learns of devices added or removed after 67 | startup. growlight discovers block devices via these directories, and through 68 | those block devices finds controllers. Controllers which do not have block 69 | devices attaches will thus not generally be found (growlight will remain aware 70 | of an adapter from which all devices are removed while it's running). 71 | 72 | The highest level of structure in growlight is the controller ("controller" and 73 | "adapter" are used interchangeably in growlight). A virtual controller is also 74 | defined, to collect various virtual devices (especially aggregates). In the 75 | fullscreen view, controllers are boxes labeled by their type, bus path, and 76 | bandwidth. Below, we see a machine with one SATA SSD, a dmcrypt device mapper 77 | block built atop that, and an unloaded SD card reader hanging off USB 3.0: 78 | 79 | Navigate among the adapters using PgUp and PgDn. Bring up the details subscreen 80 | with `v` to see full details about the adapter (along with other information). 81 | Within an adapter, up and down moves between block devices, and left and right 82 | move between partitions. Vi keys are also supported. 83 | 84 | In the readline mode, adapters are listed via the `adapter` command (`-v` can 85 | be provided to `adapter` for full details of attached devices and filesystems): 86 | 87 | ``` 88 | [growlight](0)> adapter 89 | [ahci-0] Southbridge device 0000:00.17.0 90 | Intel Corporation Sunrise Point-LP SATA Controller [AHCI mode] 91 | Virtual devices 92 | [xhci_pci-0] Southbridge device 0000:00.14.0 93 | Intel Corporation Sunrise Point-LP USB 3.0 xHCI Controller 94 | [growlight](0)> 95 | ``` 96 | -------------------------------------------------------------------------------- /doc/growlight-1.2.8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dankamongmen/growlight/b360dcc67279fca0f5e493876d81b173a6d5dc19/doc/growlight-1.2.8.png -------------------------------------------------------------------------------- /doc/growlight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dankamongmen/growlight/b360dcc67279fca0f5e493876d81b173a6d5dc19/doc/growlight.jpg -------------------------------------------------------------------------------- /doc/man/man8/growlight.8.md: -------------------------------------------------------------------------------- 1 | % growlight(8) 2 | % nick black 3 | % v1.2.38 4 | 5 | # NAME 6 | 7 | growlight - Block device and filesystem editor 8 | 9 | # SYNOPSIS 10 | 11 | **growlight** [**-h|--help**] [**-i|--import**] [**-v|--verbose**] 12 | [**-V|--version**] [**--disphelp**] [**-t path|--target=path**] 13 | 14 | # DESCRIPTION 15 | 16 | **growlight** detects and describes disk pools, block devices, partition 17 | tables, and partitions. It can partition devices, manipulate ZFS, MD, DM, LVM 18 | and hardware RAID virtual devices, and prepare an fstab file for using the 19 | devices to boot or in a chroot, and is fully aware of variable sector sizes, 20 | GPT, and UEFI. **growlight** facilitates use of UUID/WWN- and HBA-based 21 | identification of block devices. 22 | 23 | This page describes the fullscreen **notcurses(3notcurses)** implementation. 24 | Consult **growlight-readline(8)** for a line-oriented **readline(3)** 25 | variant. 26 | 27 | # OPTIONS 28 | 29 | **-h|--help**: Print a brief usage summary, and exit. 30 | 31 | **-i|--import**: Attempt to assemble aggregates (zpools, MD devices, etc) 32 | based on block device scans at startup. 33 | 34 | **-v|--verbose**: Be more verbose. 35 | 36 | **-V|--version**: Print version information and exit. 37 | 38 | **--disphelp**: Display the help subdisplay upon startup. 39 | 40 | **--notroot**: Force **growlight** to start without necessary privileges (it 41 | will usually refuse to start). 42 | 43 | **-t path|--target=path**: Run in system installation mode, using **path** 44 | as the temporary mountpoint for the target's root filesystem. "map" commands 45 | will populate the hierarchy rooted at this mountpoint. System installation mode 46 | can also be entered at run time with the "target" command. The "map" command 47 | will not result in active mounts unless **growlight** is operating in system 48 | installation mode (they will merely be used to construct target fstab output). 49 | Once system installation mode is entered, **growlight** will return 0 only as a 50 | result of a successful invocation of the "target finalize" command. **path** 51 | must exist at the time of its specification. 52 | 53 | # USAGE 54 | 55 | Press 'H' or 'F1' to toggle a help display. This display lists all available 56 | commands; commands which don't make sense at the moment are grayed out. Most 57 | of these commands are also available from the menu at the top of the screen. 58 | Press 'Alt' plus the underlined shortcut key to open a section of the menu, 59 | or click on it with a mouse. 60 | 61 | Press 'q' to exit the program. 62 | 63 | The display is hierarchal, with block devices being collected under their 64 | respective storage adapters. In addition to various physical adapters, a 65 | "virtual" adapter is provided for e.g. aggregated devices. Move among the 66 | adapters with Page Up and Page Down. Move among the block devices of an adapter 67 | with up and down; move among the partitions of a block device with left and 68 | right. Vi keys ('h'/'j'/'k'/'l') are also supported. Search with '/'; this 69 | search will be applied to all metadata. 70 | 71 | The 'G'rowlight menu allows toggling various subscreens, including the help, 72 | recent diagnostics, mount points, and a details view. Only one subscreen can 73 | be up at a time. 74 | 75 | The 'B'lockdevs menu allows you to 'm'ake a partition table (only if the 76 | selected block device doesn't already have one), 'r'emove a partition table 77 | (assuming one is present), 'W'ipe a Master Boot Record (overwriting it with 78 | zeroes), perform a 'B'ad block check, cre'A'te a new aggregate block device 79 | (e.g. an mdadm array or ZFS zpool), modify an existing aggregate with 'z', 80 | unbind an aggregate with 'Z', or set u'p' a loop device. 81 | 82 | The 'P'artitions menu allows you to make a 'n'ew partition (in empty, 83 | unallocated space, on a block device with an existing partition table), 84 | 'd'elete a partition, 's'et partition attributes, 'M'ake a filesystem, 'F'sck a 85 | filesystem, 'w'ipe a filesystem, name a filesystem 'L'abel or name, set a 86 | filesystem's 'U'uid, m'o'unt a filesystem, or unm'O'unt a filesystem. Most of 87 | these latter commands can also be applied to swap devices. 88 | 89 | When running in system installation mode (see **--target** above), the 90 | following commands are also supported: 91 | 92 | * moun't' target 93 | * unmoun'T' target 94 | * finalize UEFI with '*' 95 | * finalize BIOS with '#' 96 | * finalize fstab with '@' 97 | 98 | # BUGS 99 | 100 | Pedantic collections of ambiguous identifiers (for instance, if a label 101 | equals another device's /dev/ name) will lead to questionable results. This 102 | ought be fixed. 103 | 104 | # SEE ALSO 105 | 106 | **mount (2)**, 107 | **swapoff (2)**, 108 | **swapon (2)**, 109 | **umount (2)**, 110 | **libblkid (3)**, 111 | **notcurses (3)**, 112 | **fstab (5)**, 113 | **proc (5)**, 114 | **inotify (7)**, 115 | **udev (7)**, 116 | **blkid(8)**,, 117 | **dmraid(8)**, 118 | **dmsetup (8)**, 119 | **growlight-readline(8)**, 120 | **grub-install (8)**, 121 | **grub-mkdevicemap (8)**, 122 | **hdparm (8)**, 123 | **losetup (8)**, 124 | **lsblk (8)**, 125 | **mdadm (8)**, 126 | **mkfs (8)**, 127 | **mount (8)**, 128 | **parted (8)**, 129 | **sfdisk (8)**, 130 | **umount (8)**, 131 | **zfs (8)**, 132 | **zpool (8)** 133 | -------------------------------------------------------------------------------- /src/aggregate.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include "zfs.h" 3 | #include "mdadm.h" 4 | #include "popen.h" 5 | #include "crypt.h" 6 | #include "growlight.h" 7 | #include "aggregate.h" 8 | 9 | static int 10 | make_crypt(const char *name __attribute__ ((unused)),char * const *argv,int argc){ 11 | device *d; 12 | 13 | if(argc != 1){ 14 | diag("Wrong number of devices (%d != 1) for LUKS\n",argc); 15 | return -1; 16 | } 17 | if((d = lookup_device(*argv)) == NULL){ 18 | return -1; 19 | } 20 | return cryptondev(d); 21 | } 22 | 23 | static const aggregate_type aggregates[] = { 24 | { 25 | .name = "mdlinear", 26 | .desc = "Linear disk combination (MD)", 27 | .mindisks = 2, 28 | .maxfaulted = -1, 29 | },{ 30 | .name = "mdddf", 31 | .desc = "SNIA Data Disk Format container", 32 | .mindisks = 2, 33 | .maxfaulted = 0, 34 | },{ 35 | .name = "mdimsm", 36 | .desc = "Intel® Matrix Storage Manager container", 37 | .mindisks = 2, 38 | .maxfaulted = 0, 39 | },{ 40 | .name = "mdcontain", 41 | .desc = "Linear disk combination with metadata", 42 | .mindisks = 2, 43 | .maxfaulted = 0, 44 | },{ 45 | .name = "mdraid0", 46 | .desc = "Interleaved disk combination (striping) (MD)", 47 | .mindisks = 2, 48 | .maxfaulted = 0, 49 | .makeagg = make_mdraid0, 50 | .defname = "SprezzaRAID0", 51 | },{ 52 | .name = "mdraid1", 53 | .desc = "Mirroring (MD)", 54 | .mindisks = 2, 55 | .maxfaulted = 1, 56 | .makeagg = make_mdraid1, 57 | .defname = "SprezzaRAID1", 58 | },{ 59 | .name = "mdraid4", 60 | .desc = "Block striping with dedicated parity", 61 | .mindisks = 3, 62 | .maxfaulted = 1, 63 | .makeagg = make_mdraid4, 64 | .defname = "SprezzaRAID4", 65 | },{ 66 | .name = "mdraid5", 67 | .desc = "Block striping with distributed parity", 68 | .mindisks = 3, 69 | .maxfaulted = 1, 70 | .makeagg = make_mdraid5, 71 | .defname = "SprezzaRAID5", 72 | },{ 73 | .name = "mdraid6", 74 | .desc = "Block striping with 2x distributed parity", 75 | .mindisks = 4, 76 | .maxfaulted = 2, 77 | .makeagg = make_mdraid6, 78 | .defname = "SprezzaRAID6", 79 | },{ 80 | .name = "mdraid10", 81 | .desc = "Interleaved mirror combination", 82 | .mindisks = 4, 83 | .maxfaulted = 1, // FIXME technically 1 from each mirror 84 | .makeagg = make_mdraid10, 85 | .defname = "SprezzaRAID10", 86 | },{ 87 | .name = "zmirror", 88 | .desc = "Zpool with data replication (mirroring)", 89 | .mindisks = 2, 90 | .maxfaulted = 0, 91 | .makeagg = make_zmirror, 92 | .defname = "SprezZMirror", 93 | },{ 94 | .name = "raidz1", 95 | .desc = "ZFS RAID with distributed parity", 96 | .mindisks = 3, 97 | .maxfaulted = 0, 98 | .makeagg = make_raidz1, 99 | .defname = "SprezZRAID", 100 | },{ 101 | .name = "raidz2", 102 | .desc = "ZFS RAID with 2x distributed parity", 103 | .mindisks = 4, 104 | .maxfaulted = 0, 105 | .makeagg = make_raidz2, 106 | .defname = "SprezZRAID2", 107 | },{ 108 | .name = "raidz3", 109 | .desc = "ZFS RAID with 3x distributed parity", 110 | .mindisks = 5, 111 | .maxfaulted = 0, 112 | .makeagg = make_raidz3, 113 | .defname = "SprezZRAID3", 114 | },{ 115 | .name = "zil", 116 | .desc = "ZFS Write-Intent Log", 117 | .mindisks = 1, 118 | .maxfaulted = 0, 119 | },{ 120 | .name = "l2arc", 121 | .desc = "ZFS Level 2 Adaptive Replacement Cache", 122 | .mindisks = 1, 123 | .maxfaulted = 0, 124 | },{ 125 | .name = "dmlinear", 126 | .desc = "Linear disk combination (DM)", 127 | .mindisks = 2, 128 | // .maxfaulted // FIXME need investigate 129 | },{ 130 | .name = "dmstriped", 131 | .desc = "Interleaved disk combination (striping) (DM)", 132 | .mindisks = 2, 133 | .maxfaulted = 0, 134 | },{ 135 | .name = "dmcrypt", 136 | .desc = "LUKS block encryption (DM)", 137 | .mindisks = 1, 138 | .maxfaulted = 0, 139 | .makeagg = make_crypt, 140 | .tokenreq = L"Select an encryption passphrase.", 141 | },{ 142 | .name = "dmmirror", 143 | .desc = "Mirroring (DM)", 144 | .mindisks = 2, 145 | .maxfaulted = 1, 146 | } 147 | }; 148 | 149 | const aggregate_type *get_aggregate(const char *name){ 150 | unsigned z; 151 | 152 | for(z = 0 ; z < sizeof(aggregates) / sizeof(*aggregates) ; ++z){ 153 | if(strcmp(aggregates[z].name,name) == 0){ 154 | return &aggregates[z]; 155 | } 156 | } 157 | return NULL; 158 | } 159 | 160 | const aggregate_type *get_aggregate_types(int *count){ 161 | *count = sizeof(aggregates) / sizeof(*aggregates); 162 | return aggregates; 163 | } 164 | 165 | int assemble_aggregates(void){ 166 | int zpool = 0,mdraid = 0; 167 | const controller *c; 168 | 169 | // FIXME maybe only do it if there are both aggregable devices and 170 | // they're not currently slaves of anything? 171 | for(c = get_controllers() ; c ; c = c->next){ 172 | const device *d; 173 | 174 | for(d = c->blockdevs ; d ; d = d->next){ 175 | const device *p; 176 | 177 | if(d->mnttype){ 178 | if(zpool_p(d->mnttype)){ 179 | zpool = 1; 180 | }else if(mdraid_p(d->mnttype)){ 181 | mdraid = 1; 182 | } 183 | } 184 | for(p = d->parts ; p ; p = p->next){ 185 | if(p->mnttype){ 186 | if(zpool_p(p->mnttype)){ 187 | zpool = 1; 188 | }else if(mdraid_p(p->mnttype)){ 189 | mdraid = 1; 190 | } 191 | } 192 | } 193 | } 194 | } 195 | // probably don't always want to use -f FIXME 196 | if(zpool){ 197 | diag("Scanning for zpools...\n"); 198 | vspopen_drain("zpool import -a -f"); 199 | } 200 | if(mdraid){ 201 | diag("Scanning for MD devices...\n"); 202 | vspopen_drain("mdadm --assemble --scan"); 203 | } 204 | return 0; 205 | } 206 | -------------------------------------------------------------------------------- /src/aggregate.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_AGGREGATE 3 | #define GROWLIGHT_AGGREGATE 4 | 5 | #include "growlight.h" 6 | 7 | static inline int 8 | aggregate_default_p(const char *aggtype){ 9 | return !strcmp(aggtype,"raidz2"); 10 | } 11 | 12 | static inline int 13 | zpool_p(const char *mnttype){ 14 | if(strcmp(mnttype,"zfs_member") == 0){ 15 | return 1; 16 | } 17 | return 0; 18 | } 19 | 20 | static inline int 21 | mdraid_p(const char *mnttype){ 22 | if(strcmp(mnttype,"linux_raid_member") == 0){ 23 | return 1; 24 | } 25 | return 0; 26 | } 27 | 28 | static inline int 29 | mnttype_aggregablep(const char *mnttype){ 30 | if(mnttype == NULL){ 31 | return 1; 32 | }else if(zpool_p(mnttype)){ 33 | return 1; 34 | }else if(mdraid_p(mnttype)){ 35 | return 1; 36 | } 37 | return 0; 38 | } 39 | 40 | static inline int 41 | device_aggregablep(const device *d){ 42 | if(!mnttype_aggregablep(d->mnttype)){ 43 | return 0; 44 | } 45 | if(d->slave){ 46 | return 0; 47 | } 48 | if(d->size == 0){ 49 | return 0; 50 | } 51 | if(d->roflag){ 52 | return 0; 53 | } 54 | switch(d->layout){ 55 | case LAYOUT_NONE: 56 | if(d->blkdev.unloaded){ 57 | return 0; 58 | } 59 | if(d->blkdev.pttable){ 60 | return 0; 61 | } 62 | break; 63 | case LAYOUT_PARTITION: 64 | if(!parttype_aggregablep(d->partdev.ptype)){ 65 | return 0; 66 | } 67 | break; 68 | case LAYOUT_MDADM: 69 | case LAYOUT_DM: 70 | case LAYOUT_ZPOOL: 71 | break; 72 | } 73 | return 1; 74 | } 75 | 76 | const aggregate_type *get_aggregate(const char *); 77 | 78 | int assemble_aggregates(void); 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /src/apm.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "apm.h" 11 | #include "ptypes.h" 12 | #include "ptable.h" 13 | #include "growlight.h" 14 | 15 | #define DEFAULT_APM_ENTRIES 32 16 | #define LBA_SIZE 512u 17 | 18 | static const uint8_t APM_SIG[2] = { 0x4d, 0x50 }; 19 | 20 | // 512-byte apm partition entry. As many of these as can be fit between the 21 | // Driver Description Block / Driver Description Record (sector 0) and the data 22 | // partitions. They begin at sector 1. 23 | typedef struct __attribute__ ((packed)) apm_entry { 24 | uint16_t signature; // APM_SIG (0x504d) 25 | uint16_t reserved1; 26 | uint32_t partition_count; 27 | uint32_t fsector; 28 | uint32_t sectorcount; 29 | char pname[32]; // ASCIIZ 30 | char ptype[32]; // ASCIIZ 31 | uint32_t data_fsector; 32 | uint32_t data_sectorcount; 33 | uint32_t flags; 34 | uint32_t boot_fsector; 35 | uint32_t boot_sectorcount; 36 | uint32_t boot_address; 37 | uint32_t reserved2; 38 | uint32_t boot_entry; 39 | uint32_t reserved3; 40 | uint32_t boot_cksum; 41 | uint8_t proctype[16]; 42 | uint8_t reserved4[376]; 43 | } apm_entry; 44 | 45 | // Initialize an apple partition map having block 0 starting at |map|, on a 46 | // disk having |sectors| |lba|B sectors. Reserve space for |entries| entries. 47 | static int 48 | initialize_apm(void *map, size_t lba, uintmax_t sectors, unsigned entries){ 49 | apm_entry *apm; 50 | unsigned z; 51 | 52 | assert(map && lba && sectors && entries); 53 | if(entries + 1 > sectors){ 54 | diag("Can't place %u partition entries in %ju sectors\n", entries, sectors); 55 | return -1; 56 | } 57 | if(lba != sizeof(*apm)){ 58 | diag("Can't work with %zub LBA (need %zu)\n", lba, sizeof(*apm)); 59 | return -1; 60 | } 61 | apm = map; 62 | // Zero out the first sector (Device Descriptor Block) 63 | memset(apm, 0, lba); 64 | // Zero out each entry, marking it as a free entry 65 | for(z = 0 ; z < entries ; ++z){ 66 | apm = (apm_entry *)((char *)apm + lba); 67 | memset(apm, 0, lba); 68 | memcpy(&apm->signature, APM_SIG, sizeof(apm->signature)); 69 | apm->partition_count = entries; 70 | strcpy(apm->ptype, "Apple_Extra"); 71 | } 72 | // Span the entirety of the disk with the first partition 73 | apm = (apm_entry *)((char *)map + lba); 74 | apm->fsector = entries; 75 | apm->sectorcount = sectors - (entries + 1); 76 | strcpy(apm->pname, "Extra"); 77 | strcpy(apm->ptype, "Apple_Free"); 78 | // FIXME probably more to do here... 79 | return 0; 80 | } 81 | 82 | // Write out a apm on the device represented by fd, using lbasize-byte LBA. We 83 | // can either zero it all out, or create a new empty apm. Set realdata not 84 | // equal to 0 to perform the latterj 85 | static int 86 | write_apm(int fd, ssize_t lbasize, uintmax_t sectors, unsigned realdata){ 87 | int pgsize = getpagesize(); 88 | apm_entry *mhead; 89 | size_t mapsize; 90 | void *map; 91 | 92 | assert(pgsize > 0 && pgsize % lbasize == 0); 93 | mapsize = lbasize * (DEFAULT_APM_ENTRIES + 1); 94 | mapsize = ((mapsize / pgsize) + !!(mapsize % pgsize)) * pgsize; 95 | assert(mapsize % pgsize == 0 && mapsize); 96 | map = mmap(NULL, mapsize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 97 | if(map == MAP_FAILED){ 98 | diag("Error mapping %zub at %d (%s?)\n", mapsize, fd, strerror(errno)); 99 | return -1; 100 | } 101 | mhead = (apm_entry *)map; 102 | if(!realdata){ 103 | memset(mhead, 0, lbasize * (sectors > DEFAULT_APM_ENTRIES + 1 ? 104 | DEFAULT_APM_ENTRIES + 1 : sectors)); 105 | }else{ 106 | if(initialize_apm(mhead, lbasize, sectors, DEFAULT_APM_ENTRIES)){ 107 | munmap(map, mapsize); 108 | return -1; 109 | } 110 | } 111 | if(msync(map, mapsize, MS_SYNC|MS_INVALIDATE)){ 112 | diag("Error syncing %d (%s?)\n", fd, strerror(errno)); 113 | munmap(map, mapsize); 114 | return -1; 115 | } 116 | if(munmap(map, mapsize)){ 117 | diag("Error unmapping %d (%s?)\n", fd, strerror(errno)); 118 | return -1; 119 | } 120 | return 0; 121 | } 122 | 123 | int new_apm(device *d){ 124 | int fd; 125 | 126 | if(d->layout != LAYOUT_NONE){ 127 | diag("Won't create partition table on non-disk %s\n", d->name); 128 | return -1; 129 | } 130 | if(d->size % LBA_SIZE){ 131 | diag("Won't create apm on (%ju %% %u == %juB) disk %s\n", 132 | d->size, LBA_SIZE, d->size % LBA_SIZE, d->name); 133 | return -1; 134 | } 135 | if(d->size < LBA_SIZE){ 136 | diag("Won't create apm on empty disk %s\n", d->name); 137 | return -1; 138 | } 139 | if((fd = openat(devfd, d->name, O_RDWR|O_CLOEXEC|O_DIRECT)) < 0){ 140 | diag("Couldn't open %s (%s?)\n", d->name, strerror(errno)); 141 | return -1; 142 | } 143 | if(write_apm(fd, LBA_SIZE, d->size / LBA_SIZE, 1)){ 144 | close(fd); 145 | return -1; 146 | } 147 | if(fsync(fd)){ 148 | diag("Warning: error syncing %d for %s (%s?)\n", fd, d->name, strerror(errno)); 149 | } 150 | if(close(fd)){ 151 | diag("Error closing %d for %s (%s?)\n", fd, d->name, strerror(errno)); 152 | return -1; 153 | } 154 | return 0; 155 | } 156 | 157 | int zap_apm(device *d){ 158 | int fd; 159 | 160 | if(d->layout != LAYOUT_NONE){ 161 | diag("Won't zap partition table on non-disk %s\n", d->name); 162 | return -1; 163 | } 164 | if(d->blkdev.pttable == NULL || strcmp(d->blkdev.pttable, "apm")){ 165 | diag("No apm on disk %s\n", d->name); 166 | return -1; 167 | } 168 | if(d->size < LBA_SIZE || d->size % LBA_SIZE){ 169 | diag("Won't zap apm on (%ju %% %u == %juB) disk %s\n", 170 | d->size, LBA_SIZE, d->size % LBA_SIZE, d->name); 171 | return -1; 172 | } 173 | if((fd = openat(devfd, d->name, O_RDWR|O_CLOEXEC|O_DIRECT)) < 0){ 174 | diag("Couldn't open %s (%s?)\n", d->name, strerror(errno)); 175 | return -1; 176 | } 177 | if(write_apm(fd, LBA_SIZE, d->size / LBA_SIZE, 0)){ 178 | close(fd); 179 | return -1; 180 | } 181 | if(fsync(fd)){ 182 | diag("Warning: error syncing %d for %s (%s?)\n", fd, d->name, strerror(errno)); 183 | } 184 | if(close(fd)){ 185 | diag("Error closing %d for %s (%s?)\n", fd, d->name, strerror(errno)); 186 | return -1; 187 | } 188 | return 0; 189 | } 190 | 191 | // Map the first Apple Partition Map entry, and the disk's first sector 192 | static void * 193 | map_apm(const device *d, size_t *mapsize, int *fd, size_t lbasize){ 194 | const int pgsize = getpagesize(); 195 | void *map; 196 | 197 | if(pgsize < 0){ 198 | diag("Bad pgsize for apm: %d\n", pgsize); 199 | return MAP_FAILED; 200 | } 201 | if((*fd = openat(devfd, d->name, O_RDWR|O_CLOEXEC|O_DIRECT)) < 0){ 202 | diag("Couldn't open %s (%s?)\n", d->name, strerror(errno)); 203 | return MAP_FAILED; 204 | } 205 | *mapsize = lbasize * (DEFAULT_APM_ENTRIES + 1); 206 | *mapsize = ((*mapsize / pgsize) + !!(*mapsize % pgsize)) * pgsize; 207 | assert(*mapsize % pgsize == 0); 208 | map = mmap(NULL, *mapsize, PROT_READ|PROT_WRITE, MAP_SHARED, *fd, 0); 209 | if(map == MAP_FAILED){ 210 | diag("Couldn't map apm header (%s?)\n", strerror(errno)); 211 | close(*fd); 212 | return map; 213 | } 214 | return map; 215 | } 216 | 217 | // Pass the return from map_apm(), ie the MBR boot sector 218 | static int 219 | unmap_apm(const device *parent, void *map, size_t mapsize, int fd){ 220 | const int pgsize = getpagesize(); 221 | 222 | assert(parent->layout == LAYOUT_NONE); 223 | if(pgsize < 0){ 224 | diag("Warning: bad pgsize for apm: %d\n", pgsize); 225 | } 226 | if(munmap(map, mapsize)){ 227 | int e = errno; 228 | 229 | diag("Error munmapping %s (%s?)\n", parent->name, strerror(errno)); 230 | close(fd); 231 | errno = e; 232 | return -1; 233 | } 234 | return 0; 235 | } 236 | 237 | uintmax_t first_apm(const device *d){ 238 | uintmax_t fsector; 239 | size_t mapsize; 240 | apm_entry *apm; 241 | void *map; 242 | int fd; 243 | 244 | if((map = map_apm(d, &mapsize, &fd, LBA_SIZE)) == MAP_FAILED){ 245 | return -1; 246 | } 247 | if(mapsize < LBA_SIZE * (DEFAULT_APM_ENTRIES + 1)){ 248 | diag("APM size too small (%zu < %u)\n", mapsize, LBA_SIZE * (DEFAULT_APM_ENTRIES + 1)); 249 | unmap_apm(d, map, mapsize, fd); 250 | return -1; 251 | } 252 | apm = (apm_entry *)((char *)map + LBA_SIZE); 253 | fsector = 1 + apm->partition_count; 254 | unmap_apm(d, map, mapsize, fd); 255 | return fsector; 256 | } 257 | 258 | uintmax_t last_apm(const device *d){ 259 | return d->logsec && d->size ? d->size / d->logsec - 1 : 0; 260 | } 261 | -------------------------------------------------------------------------------- /src/apm.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_APM 3 | #define GROWLIGHT_APM 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | 11 | struct device; 12 | 13 | // Pass the block device 14 | int new_apm(struct device *); 15 | int zap_apm(struct device *); 16 | 17 | uintmax_t first_apm(const struct device *); 18 | uintmax_t last_apm(const struct device *); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/crypt.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include // libcryptsetup.h needs size_t 3 | #include 4 | 5 | #include "crypt.h" 6 | #include "growlight.h" 7 | 8 | // Create LUKS on the device 9 | int cryptondev(device *d){ 10 | struct crypt_params_luks1 params; 11 | struct crypt_device *cctx; 12 | char path[PATH_MAX + 1]; 13 | 14 | if((unsigned)snprintf(path, sizeof(path), "/dev/%s", d->name) >= sizeof(path)){ 15 | diag("Bad path: /dev/%s\n", d->name); 16 | return -1; 17 | } 18 | if(crypt_init(&cctx, path)){ 19 | diag("Couldn't create LUKS context for %s\n", d->name); 20 | return -1; 21 | } 22 | memset(¶ms, 0, sizeof(params)); 23 | params.hash = "sha1"; 24 | if(crypt_format(cctx, CRYPT_LUKS1, "aes", "xts-plain64", NULL, NULL, 256 / CHAR_BIT, ¶ms)){ 25 | diag("Couldn't format LUKS on %s\n", d->name); 26 | crypt_free(cctx); 27 | return -1; 28 | } 29 | // FIXME acquire a real passphrase! 30 | if(crypt_keyslot_add_by_volume_key(cctx, CRYPT_ANY_SLOT, NULL, 0, "r00tme", 6) < 0){ 31 | diag("Couldn't open LUKS on %s\n", d->name); 32 | crypt_free(cctx); 33 | return -1; 34 | } 35 | crypt_free(cctx); 36 | return 0; 37 | } 38 | 39 | int crypt_start(void){ 40 | return 0; 41 | } 42 | 43 | int crypt_stop(void){ 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /src/crypt.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_CRYPT 3 | #define GROWLIGHT_CRYPT 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | 11 | // Create LUKS on the device 12 | int cryptondev(struct device *); 13 | 14 | int crypt_start(void); 15 | int crypt_stop(void); 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/dm.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | 6 | #include "dm.h" 7 | #include "sysfs.h" 8 | #include "growlight.h" 9 | 10 | int explore_dm_sysfs(device *d,int dirfd){ 11 | /*unsigned long rd; 12 | mdslave **enqm;*/ 13 | 14 | d->dmdev.disks = 1; 15 | if((d->model = strdup("Linux devmapper")) == NULL){ 16 | return -1; 17 | } 18 | if((d->dmdev.uuid = get_sysfs_string(dirfd,"uuid")) == NULL){ 19 | verbf("Warning: no 'uuid' content in dm device %s\n",d->name); 20 | } 21 | if((d->dmdev.dmname = get_sysfs_string(dirfd,"name")) == NULL){ 22 | verbf("Warning: no 'name' content in dm device %s\n",d->name); 23 | } 24 | d->dmdev.transport = AGGREGATE_UNKNOWN; 25 | // FIXME need to browse slaves/ subdirectory in sysfs entry 26 | /*enqm = &d->dmdev.slaves; 27 | for(rd = 0 ; rd < d->dmdev.disks ; ++rd){ 28 | char buf[NAME_MAX],lbuf[NAME_MAX],*c; 29 | device *subd; 30 | mdslave *m; 31 | int r; 32 | 33 | if(snprintf(buf,sizeof(buf),"rd%lu",rd) >= (int)sizeof(buf)){ 34 | diag("Couldn't look up raid device %lu\n",rd); 35 | errno = ENAMETOOLONG; 36 | return -1; 37 | } 38 | r = readlinkat(dirfd,buf,lbuf,sizeof(lbuf)); 39 | if((r < 0 && errno != ENOENT) || r >= (int)sizeof(lbuf)){ 40 | int e = errno; 41 | 42 | diag("Couldn't look up slave %s (%s?)\n",buf,strerror(errno)); 43 | errno = e; 44 | return -1; 45 | }else if(r < 0 && errno == ENOENT){ 46 | // missing/faulted device -- represent somehow? 47 | continue; 48 | } 49 | lbuf[r] = '\0'; 50 | if(strncmp(lbuf,"dev-",4)){ 51 | diag("Couldn't get device from %s\n",lbuf); 52 | return -1; 53 | } 54 | if((c = strdup(lbuf + 4)) == NULL){ 55 | return -1; 56 | } 57 | if((m = malloc(sizeof(*m))) == NULL){ 58 | free(c); 59 | return -1; 60 | } 61 | m->name = c; 62 | m->next = NULL; 63 | *enqm = m; 64 | enqm = &m->next; 65 | //m->component = subd; 66 | lock_growlight(); 67 | if((subd = lookup_device(c)) == NULL){ 68 | unlock_growlight(); 69 | return -1; 70 | } 71 | switch(subd->layout){ 72 | case LAYOUT_NONE: 73 | if(d->dmdev.transport == AGGREGATE_UNKNOWN){ 74 | d->dmdev.transport = subd->blkdev.transport; 75 | }else if(d->dmdev.transport != subd->blkdev.transport){ 76 | d->dmdev.transport = AGGREGATE_MIXED; 77 | } 78 | break; 79 | case LAYOUT_MDADM: 80 | if(d->dmdev.transport == AGGREGATE_UNKNOWN){ 81 | d->dmdev.transport = subd->dmdev.transport; 82 | }else if(d->dmdev.transport != subd->dmdev.transport){ 83 | d->dmdev.transport = AGGREGATE_MIXED; 84 | } 85 | break; 86 | case LAYOUT_PARTITION: 87 | if(d->dmdev.transport == AGGREGATE_UNKNOWN){ 88 | d->dmdev.transport = subd->partdev.parent->blkdev.transport; 89 | }else if(d->dmdev.transport != subd->partdev.parent->blkdev.transport){ 90 | d->dmdev.transport = AGGREGATE_MIXED; 91 | } 92 | break; 93 | case LAYOUT_ZPOOL: 94 | if(d->dmdev.transport == AGGREGATE_UNKNOWN){ 95 | d->dmdev.transport = subd->zpool.transport; 96 | }else if(d->dmdev.transport != subd->zpool.transport){ 97 | d->dmdev.transport = AGGREGATE_MIXED; 98 | } 99 | break; 100 | default: 101 | diag("Unknown layout %d on %s\n",subd->layout,subd->name); 102 | break; 103 | } 104 | unlock_growlight(); 105 | }*/ 106 | return 0; 107 | } 108 | -------------------------------------------------------------------------------- /src/dm.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_DM 3 | #define GROWLIGHT_DM 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | 11 | int explore_dm_sysfs(struct device *,int); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/dmi.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include "sysfs.h" 6 | #include "growlight.h" 7 | 8 | #define DMI_PATH "/sys/devices/virtual/dmi/id" 9 | 10 | static char *bios_vendor; 11 | static char *bios_version; 12 | 13 | int dmi_init(void){ 14 | int dfd; 15 | 16 | if((dfd = open(DMI_PATH, O_RDONLY|O_DIRECTORY)) < 0){ 17 | diag("Couldn't open %s (%s)\n", DMI_PATH, strerror(errno)); 18 | return -1; 19 | } 20 | if((bios_version = get_sysfs_string(dfd, "bios_version")) == NULL){ 21 | diag("Couldn't open %s/%s (%s)\n", DMI_PATH, "bios_version", strerror(errno)); 22 | } 23 | if((bios_vendor = get_sysfs_string(dfd, "bios_vendor")) == NULL){ 24 | diag("Couldn't open %s/%s (%s)\n", DMI_PATH, "bios_vendor", strerror(errno)); 25 | } 26 | return 0; 27 | } 28 | 29 | const char *get_bios_version(void){ 30 | return bios_version; 31 | } 32 | 33 | const char *get_bios_vendor(void){ 34 | return bios_vendor; 35 | } 36 | -------------------------------------------------------------------------------- /src/dmi.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_DMI 3 | #define GROWLIGHT_DMI 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | // Load the DMI configuration from sysfs 10 | int dmi_init(void); 11 | 12 | const char *get_bios_version(void); 13 | const char *get_bios_vendor(void); 14 | 15 | #ifdef __cplusplus 16 | } 17 | #endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/fs.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_FS 3 | #define GROWLIGHT_FS 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | struct growlight_ui; 11 | 12 | #include 13 | #include 14 | 15 | // Create the given type of filesystem on this device 16 | int make_filesystem(struct device *,const char *,const char *); 17 | int parse_filesystems(const struct growlight_ui *,const char *); 18 | int wipe_filesystem(struct device *); 19 | 20 | static inline int 21 | fstype_default_p(const char *fstype){ 22 | return !strcmp(fstype, "ext4"); 23 | } 24 | 25 | static inline int 26 | fstype_swap_p(const char *fstype){ 27 | return !strcmp(fstype, "swap"); 28 | } 29 | 30 | static inline int 31 | aggregate_fs_p(const char *fstype){ 32 | return !strcmp(fstype,"linux_raid_member") || 33 | !strcmp(fstype, "zfs_member"); 34 | } 35 | 36 | struct mkfsmarshal { 37 | const char *name; // supply this label, if possible 38 | int force; // supply a force directive, if one exists 39 | uintmax_t stride; // opt. for raid array of strideB chunks 40 | uintmax_t swidth; // opt. for raid array of swidth stripe width 41 | }; 42 | 43 | // Does the filesystem support the concept of a name/label? 44 | int fstype_named_p(const char *); 45 | 46 | // Does the filesystem support the concept of a UUID? 47 | int fstype_uuid_p(const char *); 48 | 49 | // Is the filesystem virtual (not backed by a single device entry)? 50 | int fstype_virt_p(const char *); 51 | 52 | #ifdef __cplusplus 53 | } 54 | #endif 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/gpt.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_GPT 3 | #define GROWLIGHT_GPT 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | #include 11 | 12 | #define GUIDSIZE 16 // 128 opaque bits 13 | 14 | struct device; 15 | 16 | // Pass the block device 17 | int new_gpt(struct device *); 18 | int zap_gpt(struct device *); 19 | int add_gpt(struct device *,const wchar_t *,uintmax_t,uintmax_t,unsigned long long); 20 | 21 | // Pass the partition 22 | int del_gpt(const struct device *); 23 | int name_gpt(struct device *,const wchar_t *); 24 | int uuid_gpt(struct device *,const void *); 25 | int flag_gpt(struct device *,uint64_t,unsigned); 26 | int flags_gpt(struct device *,uint64_t); 27 | int code_gpt(struct device *,unsigned long long); 28 | 29 | uintmax_t first_gpt(const struct device *); 30 | uintmax_t last_gpt(const struct device *); 31 | 32 | // One LBA block, padded with zeroes at the end. 92 bytes. 33 | typedef struct __attribute__ ((packed)) gpt_header { 34 | uint64_t signature; // "EFI PART", 45 46 49 20 50 41 52 54 35 | uint32_t revision; // Through UEFI 2.3.1: 00 00 01 00 36 | uint32_t headsize; // Header size in little endian, 37 | // excludes padding: 5c 00 00 00 == 92 38 | // byte 0x10 39 | uint32_t crc; // crc32, 0 through headsize 40 | uint32_t reserved; // must be 0 41 | uint64_t lba; // location of this header 42 | // byte 0x20 43 | uint64_t backuplba; // location of backup header (should be 44 | // last sector of disk) 45 | uint64_t first_usable; // first usable lba 46 | // byte 0x30 47 | uint64_t last_usable; // last usable lba 48 | unsigned char disk_guid[GUIDSIZE]; 49 | uint64_t partlba; // partition entries lba for this copy 50 | // byte 0x50 51 | uint32_t partcount; // supported partition entry count 52 | uint32_t partsize; // size of partition entries 53 | uint32_t partcrc; // crc32 of partition array 54 | } gpt_header; 55 | 56 | // 128-byte GUID partition entry. A GPT table must provide space for at least 57 | // MINIMUM_GPT_ENTRIES (128) of these, equal to 16k (32 512-byte sectors, or 58 | // 4 4096-byte sectors) in both copies of the GPT. 59 | typedef struct __attribute__ ((packed)) gpt_entry { 60 | unsigned char type_guid[GUIDSIZE]; 61 | unsigned char part_guid[GUIDSIZE]; 62 | uint64_t first_lba; 63 | uint64_t last_lba; 64 | uint64_t flags; 65 | uint16_t name[36]; // 36 UTF-16LE code units 66 | } gpt_entry; 67 | 68 | // Update CRCs over GPT header and (->partcount >= MINIMUM_GPT_ENTRIES) GPT PEs 69 | int update_crc(gpt_header *head, const gpt_entry *gpes); 70 | 71 | // Initialize the 'lbasize'-byte sector headed by 'gh' as a GPT primary. The 72 | // backup is at sector 'backuplba'. 'firstusable' is the first LBA at which an 73 | // actual partition may be placed. Provide a GUIDSIZE-byte UUID, or NULL for a 74 | // random one to be generated. 75 | int initialize_gpt(gpt_header *gh, size_t lbasize, uint64_t backuplba, 76 | uint64_t firstusable, const void* uuid); 77 | 78 | uint32_t host_to_le32(uint32_t x); 79 | uint64_t host_to_le64(uint64_t x); 80 | 81 | #ifdef __cplusplus 82 | } 83 | #endif 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /src/health.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "popen.h" 11 | #include "health.h" 12 | #include "growlight.h" 13 | 14 | int badblock_scan(device *d, unsigned rw){ 15 | char cmd[PATH_MAX]; 16 | 17 | if(d->layout != LAYOUT_NONE){ 18 | diag("Block scans are performed only on raw block devices\n"); 19 | return -1; 20 | } 21 | // FIXME supply -b blocksize argument! 22 | if(snprintf(cmd, sizeof(cmd), "badblocks -v -s %s /dev/%s", 23 | rw ? d->mnt.count ? "-n" : "-w" : "", d->name) >= (int)sizeof(cmd)){ 24 | diag("Bad name: %s\n", d->name); 25 | return -1; 26 | } 27 | if(popen_drain(cmd)){ 28 | return -1; 29 | } 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /src/health.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_HEALTH 3 | #define GROWLIGHT_HEALTH 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | 11 | int badblock_scan(struct device *,unsigned); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/libblkid.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "fs.h" 12 | #include "libblkid.h" 13 | #include "growlight.h" 14 | 15 | static blkid_cache cache; 16 | static unsigned cache_once_success; 17 | static pthread_once_t cache_once = PTHREAD_ONCE_INIT; 18 | 19 | static void 20 | init_blkid_cache(void){ 21 | if(blkid_get_cache(&cache, NULL) == 0){ 22 | if(blkid_probe_all(cache) == 0){ 23 | blkid_gc_cache(cache); 24 | cache_once_success = 1; 25 | } 26 | } 27 | } 28 | 29 | // Call at the start of all entry points, pairing with blkid_exit on exit. 30 | // Ensures the libblkid block cache is successfully initialized. 31 | static int 32 | blkid_entry(void){ 33 | if(pthread_once(&cache_once, init_blkid_cache)){ 34 | return -1; 35 | } 36 | if(!cache_once_success){ 37 | return -1; 38 | } 39 | return 0; 40 | } 41 | 42 | static inline int 43 | blkid_exit(int ret){ 44 | return ret; 45 | } 46 | 47 | int close_blkid(void){ 48 | if(blkid_entry()){ 49 | return -1; 50 | } 51 | blkid_put_cache(cache); 52 | return blkid_exit(0); 53 | } 54 | 55 | #include 56 | // Takes a /dev/ path, and examines the superblock therein for a valid 57 | // filesystem or raid superblock. 58 | int probe_blkid_superblock(const char *dev, blkid_probe *sbp, device *d){ 59 | char buf[PATH_MAX], *mnttype, *uuid, *label, *partuuid; 60 | const char *val, *name; 61 | unsigned parttype; 62 | blkid_probe bp; 63 | wchar_t *pname; 64 | size_t len; 65 | int n; 66 | 67 | pname = NULL; 68 | parttype = 0; 69 | partuuid = uuid = label = mnttype = NULL; 70 | if(strncmp(dev, "/dev/", 5)){ 71 | if(snprintf(buf, sizeof(buf), "/dev/%s", dev) >= (int)sizeof(buf)){ 72 | diag("Bad name: %s\n", dev); 73 | return -1; 74 | } 75 | dev = buf; 76 | } 77 | // This will sometimes fail due to the device node not yet existing. To 78 | // get here, however, we had to receive the name in a udev message, or 79 | // via discovery -- we've verified a /sys block entry. Go ahead and 80 | // loop a time or two, even though it's gross. I'd like a better way to 81 | // deal with this, obviously. FIXME 82 | { 83 | int i; 84 | 85 | for(i = 0 ; i < 3 ; ++i){ 86 | if((bp = blkid_new_probe_from_filename(dev)) == NULL){ 87 | if(errno == ENOMEDIUM){ 88 | verbf("Couldn't get blkid probe for %s (%s)\n", dev, strerror(errno)); 89 | return -1; 90 | } 91 | diag("Couldn't get blkid probe for %s (%s), retrying...\n", dev, strerror(errno)); 92 | sleep(1); 93 | }else{ 94 | break; 95 | } 96 | } 97 | if(bp == NULL){ 98 | if((bp = blkid_new_probe_from_filename(dev)) == NULL){ 99 | if(errno != ENOMEDIUM){ 100 | diag("Couldn't get blkid probe for %s (%s), retrying...\n", dev, strerror(errno)); 101 | return -1; 102 | } 103 | return blkid_exit(-1); 104 | } 105 | } 106 | } 107 | if(blkid_probe_enable_topology(bp, 1)){ 108 | diag("Couldn't enable blkid topology for %s (%s)\n", dev, strerror(errno)); 109 | return blkid_exit(-1); 110 | } 111 | if(blkid_probe_enable_partitions(bp, 1)){ 112 | diag("Couldn't enable blkid partitionprobe for %s (%s)\n", dev, strerror(errno)); 113 | goto err; 114 | } 115 | if(blkid_probe_set_partitions_flags(bp, BLKID_PARTS_ENTRY_DETAILS)){ 116 | diag("Couldn't set blkid partitionflags for %s (%s)\n", dev, strerror(errno)); 117 | goto err; 118 | } 119 | if(blkid_probe_enable_superblocks(bp, 1)){ 120 | diag("Couldn't enable blkid superprobe for %s (%s)\n", dev, strerror(errno)); 121 | goto err; 122 | } 123 | if(blkid_probe_set_superblocks_flags(bp, BLKID_SUBLKS_DEFAULT | BLKID_SUBLKS_VERSION)){ 124 | diag("Couldn't set blkid superflags for %s (%s)\n", dev, strerror(errno)); 125 | goto err; 126 | } 127 | if(blkid_do_fullprobe(bp)){ 128 | diag("Couldn't run blkid fullprobe for %s (%s)\n", dev, strerror(errno)); 129 | goto err; 130 | } 131 | n = blkid_probe_numof_values(bp); 132 | while(n--){ 133 | blkid_probe_get_value(bp, n, &name, &val, &len); 134 | if(strcmp(name, "TYPE") == 0){ 135 | if(strcmp(val, "swap") == 0){ 136 | if((mnttype = strdup("swap")) == NULL){ 137 | goto err; 138 | } 139 | if(d->swapprio == SWAP_INVALID){ 140 | d->swapprio = SWAP_INACTIVE; 141 | } 142 | }else if(blkid_known_fstype(val)){ 143 | if((mnttype = strdup(val)) == NULL){ 144 | goto err; 145 | } 146 | }else{ 147 | diag("Warning: unknown type %s for %s\n", val, dev); 148 | } 149 | }else if(strcmp(name, "UUID") == 0){ 150 | if((uuid = strdup(val)) == NULL){ 151 | goto err; 152 | } 153 | }else if(strcmp(name, "LABEL") == 0){ 154 | if((label = strdup(val)) == NULL){ 155 | goto err; 156 | } 157 | }else if(strcmp(name, "PART_ENTRY_SIZE") == 0){ 158 | if(d->layout == LAYOUT_PARTITION){ 159 | unsigned long long s; 160 | 161 | s = strtoull(val, NULL, 0); 162 | // The sizes can and do differ: 163 | // 164 | // - Extended partitions (MBR 0x5) will be 165 | // reported using their full size by libblkid, 166 | // but sysfs reports only that which doesn't 167 | // overlap with other partitions (we prefer 168 | // the latter). 169 | if(d->size && d->size != s){ 170 | verbf("%s size changed from %ju to %llu\n", d->name, d->size, s); 171 | } 172 | }else{ 173 | diag("PART_ENTRY_SIZE on non-partition %s\n", d->name); 174 | } 175 | }else if(strcmp(name, "PART_ENTRY_UUID") == 0){ 176 | if(d->layout == LAYOUT_PARTITION){ 177 | if((partuuid = strdup(val)) == NULL){ 178 | goto err; 179 | } 180 | }else{ 181 | diag("PART_ENTRY_UUID on non-partition %s\n", d->name); 182 | } 183 | }else if(strcmp(name, "PART_ENTRY_NAME") == 0){ 184 | if(d->layout == LAYOUT_PARTITION){ 185 | mbstate_t ps; 186 | pname = malloc(sizeof(*pname) * (strlen(val) + 1)); 187 | memset(&ps, 0, sizeof(ps)); 188 | mbsnrtowcs(pname, &val, strlen(val) + 1, strlen(val) + 1, &ps); 189 | }else{ 190 | diag("PART_ENTRY_NAME on non-partition %s\n", d->name); 191 | } 192 | }else if(strcmp(name, "PART_ENTRY_TYPE") == 0){ 193 | if(d->layout == LAYOUT_PARTITION){ 194 | parttype = get_str_code(val); 195 | }else{ 196 | diag("PART_ENTRY_TYPE on non-partition %s\n", d->name); 197 | } 198 | }else if(strcmp(name, "LOGICAL_SECTOR_SIZE") == 0){ 199 | d->logsec = strtoull(val, NULL, 0); 200 | }else if(strcmp(name, "PHYSICAL_SECTOR_SIZE") == 0){ 201 | d->physsec = strtoull(val, NULL, 0); 202 | }else{ 203 | verbf("attr %s=%s for %s\n", name, val, dev); 204 | } 205 | } 206 | if(d->layout == LAYOUT_PARTITION){ 207 | if(d->partdev.ptype == 0){ 208 | d->partdev.ptype = parttype; 209 | }else if(!parttype || d->partdev.ptype != parttype){ 210 | if(d->partdev.ptype){ 211 | diag("Partition type changed (%04x->%04x)\n", d->partdev.ptype, parttype); 212 | } 213 | d->partdev.ptype = parttype; 214 | } 215 | if(d->partdev.uuid == NULL){ 216 | d->partdev.uuid = partuuid; 217 | }else if(!partuuid || strcmp(d->partdev.uuid, partuuid)){ 218 | if(d->partdev.uuid){ 219 | diag("Partition UUID changed (%s->%s)\n", d->partdev.uuid, partuuid ? partuuid : "none"); 220 | } 221 | free(d->partdev.uuid); 222 | d->partdev.uuid = partuuid; 223 | } 224 | if(d->partdev.pname == NULL){ 225 | d->partdev.pname = pname; 226 | }else if(!pname || wcscmp(d->partdev.pname, pname)){ 227 | if(d->partdev.pname){ 228 | diag("Partition name changed (%ls->%ls)\n", d->partdev.pname, pname ? pname : L"none"); 229 | } 230 | free(d->partdev.pname); 231 | d->partdev.pname = pname; 232 | } 233 | } 234 | if(d->mnttype == NULL){ 235 | d->mnttype = mnttype; 236 | }else if(!mnttype || strcmp(mnttype, d->mnttype)){ 237 | if(d->mnttype){ 238 | diag("FS type changed (%s->%s)\n", d->mnttype, mnttype ? mnttype : "none"); 239 | } 240 | free(d->mnttype); 241 | d->mnttype = mnttype; 242 | }else{ 243 | free(mnttype); 244 | } 245 | if(d->uuid == NULL){ 246 | d->uuid = uuid; 247 | }else if(!uuid || strcmp(uuid, d->uuid)){ 248 | if(d->uuid){ 249 | diag("FS UUID changed (%s->%s)\n", d->uuid, uuid ? uuid : "none"); 250 | } 251 | free(d->uuid); 252 | d->uuid = uuid; 253 | }else{ 254 | free(uuid); 255 | } 256 | if(d->label == NULL){ 257 | d->label = label; 258 | }else if(!label || strcmp(label, d->label)){ 259 | if(d->label){ 260 | diag("FS label changed (%s->%s)\n", d->label, label ? label : "none"); 261 | } 262 | free(d->label); 263 | d->label = label; 264 | }else{ 265 | free(label); 266 | } 267 | if(sbp){ 268 | *sbp = bp; 269 | }else{ 270 | blkid_free_probe(bp); 271 | } 272 | return 0; 273 | 274 | err: 275 | blkid_free_probe(bp); 276 | free(mnttype); 277 | free(label); 278 | free(uuid); 279 | return -1; 280 | } 281 | -------------------------------------------------------------------------------- /src/libblkid.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_LIBBLKID 3 | #define GROWLIGHT_LIBBLKID 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | 11 | struct device; 12 | 13 | int probe_blkid_superblock(const char *,blkid_probe *,struct device *); 14 | int close_blkid(void); 15 | 16 | #ifdef __cplusplus 17 | } 18 | #endif 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/mbr.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "mbr.h" 15 | #include "sha.h" 16 | #include "growlight.h" 17 | 18 | #define MBR_SIZE 512 19 | #define MBR_CODE_SIZE 440 20 | 21 | int mbrsha1(device *d, int fd, void *buf){ 22 | // We only check the first MBR_CODE_SIZE bytes, but must read in 23 | // multiples of the sector size. This code might in fact be broken 24 | // for 4k sector disks -- I'm unsure whether it's logical or physical. 25 | // FIXME find out! 26 | unsigned char mbr[MBR_SIZE]; 27 | ssize_t r; 28 | 29 | if(lseek(fd, 0, SEEK_SET)){ 30 | int e = errno; 31 | diag("Couldn't seek to first byte of %s (%s?)\n", d->name, strerror(errno)); 32 | errno = e; 33 | return -1; 34 | } 35 | if((r = read(fd, mbr, sizeof(mbr))) < 0){ 36 | diag("Error reading %zu from %s (%s?)\n", sizeof(mbr), d->name, strerror(errno)); 37 | return -1; 38 | } 39 | if(r < (int)sizeof(mbr)){ 40 | diag("Short read %zd/%zu from %s\n", r, sizeof(mbr), d->name); 41 | return -1; 42 | } 43 | sha1(mbr, MBR_CODE_SIZE, buf); 44 | return 0; 45 | } 46 | 47 | int zerombrp(const void *buf){ 48 | const void *z = "\x63\x9a\xc5\xcd\xf8\xa5\xcf\x32\x45\x97\x59\x32\xc6\xa4\x21\x54\x50\xa7\xb9\x8f"; 49 | 50 | return !memcmp(buf, z, 20); 51 | } 52 | 53 | static inline int 54 | wipe_first_sector(device *d, size_t wipe, size_t wipeend){ 55 | static char buf[MBR_SIZE]; 56 | char dbuf[PATH_MAX]; 57 | int fd, pgsize; 58 | void *map; 59 | 60 | if((pgsize = getpagesize()) < 0){ 61 | diag("Couldn't get page size\n"); 62 | return -1; 63 | } 64 | if(wipeend > sizeof(buf) || wipe >= wipeend){ 65 | diag("Can't wipe %zu/%zu/%zu\n", wipe, wipeend, sizeof(buf)); 66 | return -1; 67 | } 68 | if(d->layout != LAYOUT_NONE){ 69 | diag("Will only wipe BIOS state for block devices\n"); 70 | return -1; 71 | } 72 | if(snprintf(dbuf, sizeof(dbuf), "/dev/%s", d->name) >= (int)sizeof(dbuf)){ 73 | diag("Bad device name: %s\n", d->name); 74 | return -1; 75 | } 76 | if((fd = openat(devfd, d->name, O_RDWR|O_CLOEXEC|O_DIRECT)) < 0){ 77 | int e = errno; 78 | diag("Couldn't open /dev/%s (%s?)\n", d->name, strerror(errno)); 79 | errno = e; 80 | return -1; 81 | } 82 | map = mmap(NULL, pgsize, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED, fd, 0); 83 | if(map == MAP_FAILED){ 84 | int e = errno; 85 | diag("Couldn't map %d from %d on %s (%s?)\n", pgsize, fd, dbuf, strerror(errno)); 86 | close(fd); 87 | errno = e; 88 | return -1; 89 | } 90 | memcpy((char *)map + wipe, buf, wipeend - wipe); 91 | if(munmap(map, pgsize)){ 92 | int e = errno; 93 | diag("Couldn't unmap %d from %d on %s (%s?)\n", pgsize, fd, dbuf, strerror(errno)); 94 | close(fd); 95 | errno = e; 96 | return -1; 97 | } 98 | if(mbrsha1(d, fd, d->blkdev.biossha1)){ 99 | int e = errno; 100 | close(fd); 101 | errno = e; 102 | return -1; 103 | } 104 | if(close(fd)){ 105 | int e = errno; 106 | diag("Couldn't close %s (%s?)\n", dbuf, strerror(errno)); 107 | errno = e; 108 | return -1; 109 | } 110 | if(zerombrp(d->blkdev.biossha1)){ 111 | d->blkdev.biosboot = 0; 112 | } 113 | // FIXME we still have valid filesystems, but no longer have valid 114 | // partition table entries for them (iff we were using MBR). add 115 | // "recovery"? gparted can supposedly find lost filesystems.... 116 | if(rescan_blockdev(d)){ 117 | return -1; 118 | } 119 | return 0; 120 | } 121 | 122 | int wipe_biosboot(device *d){ 123 | return wipe_first_sector(d, 0, MBR_CODE_SIZE); 124 | } 125 | 126 | int wipe_dosmbr(device *d){ 127 | if(wipe_first_sector(d, 0, MBR_SIZE)){ 128 | return -1; 129 | } 130 | return 0; 131 | } 132 | 133 | int wipe_dos_ptable(device *d){ 134 | if(wipe_first_sector(d, MBR_CODE_SIZE, MBR_SIZE)){ 135 | return -1; 136 | } 137 | return 0; 138 | } 139 | -------------------------------------------------------------------------------- /src/mbr.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_MBR 3 | #define GROWLIGHT_MBR 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | 11 | // Take a SHA-1 checksum over the MBR code area. fd is an open fd for a true 12 | // block device. The buffer must be able to hold 20 bytes (160 bits). The 13 | // checksum is taken over the first 444 bytes, not all 512 bytes of the MBR. 14 | int mbrsha1(struct device *, int, void *); 15 | 16 | int zerombrp(const void *); 17 | 18 | int wipe_biosboot(struct device *); 19 | int wipe_dosmbr(struct device *); 20 | int wipe_dos_ptable(struct device *); 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /src/mdadm.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "sysfs.h" 11 | #include "mdadm.h" 12 | #include "popen.h" 13 | #include "growlight.h" 14 | #include "aggregate.h" 15 | 16 | int explore_md_sysfs(device *d,int dirfd){ 17 | unsigned degraded = 0; 18 | unsigned long rd; 19 | mdslave **enqm; 20 | char *syncpct; 21 | char buf[30]; 22 | 23 | if((syncpct = get_sysfs_string(dirfd,"sync_completed")) == NULL){ 24 | verbf("Warning: no 'sync_completed' content in mdadm device %s\n",d->name); 25 | }else if(strcmp(syncpct,"none") == 0){ 26 | d->mddev.resync = 0; 27 | }else{ 28 | // FIXME lex "%ju / %ju" 29 | d->mddev.resync = 1; 30 | } 31 | free(syncpct); 32 | // These files will be empty on incomplete arrays like the md0 that 33 | // sometimes pops up. 34 | if(get_sysfs_uint(dirfd,"raid_disks",&d->mddev.disks)){ 35 | verbf("Warning: no 'raid_disks' content in mdadm device %s\n",d->name); 36 | d->mddev.disks = 0; 37 | } 38 | // Chunk size is only applicable for RAID[0456] and RAID10. 39 | // It is *not* set and *not* applicable for RAID1 or linear. 40 | if(get_sysfs_uint(dirfd,"chunk_size",&rd)){ 41 | verbf("Warning: no 'chunk_size' content in mdadm device %s\n",d->name); 42 | d->mddev.stride = 0; 43 | }else{ 44 | d->mddev.stride = rd; 45 | } 46 | if(get_sysfs_uint(dirfd,"degraded",&d->mddev.degraded)){ 47 | verbf("Warning: no 'degraded' content in mdadm device %s\n",d->name); 48 | d->mddev.degraded = 0; 49 | } 50 | if((d->mddev.level = get_sysfs_string(dirfd,"level")) == NULL){ 51 | verbf("Warning: no 'level' content in mdadm device %s\n",d->name); 52 | d->mddev.level = 0; 53 | } 54 | if((d->revision = get_sysfs_string(dirfd,"metadata_version")) == NULL){ 55 | verbf("Warning: no 'metadata_version' content in mdadm device %s\n",d->name); 56 | } 57 | // FIXME there's some archaic rules on mdadm devices making some of them 58 | // non-partitionable, but they're all partitionable after 2.6.38 or something 59 | /*if((d->mddev.pttable = strdup("mdp")) == NULL){ 60 | return -1; 61 | }*/ 62 | d->mddev.pttable = NULL; 63 | if((d->model = strdup("Linux mdadm")) == NULL){ 64 | return -1; 65 | } 66 | enqm = &d->mddev.slaves; 67 | d->mddev.transport = AGGREGATE_UNKNOWN; 68 | for(rd = 0 ; rd < d->mddev.disks ; ++rd){ 69 | char rbuf[NAME_MAX],lbuf[NAME_MAX],*c; 70 | device *subd; 71 | mdslave *m; 72 | int r; 73 | 74 | if(snprintf(rbuf,sizeof(rbuf),"rd%lu",rd) >= (int)sizeof(rbuf)){ 75 | diag("Couldn't look up raid device %lu\n",rd); 76 | errno = ENAMETOOLONG; 77 | return -1; 78 | } 79 | r = readlinkat(dirfd,rbuf,lbuf,sizeof(lbuf)); 80 | if((r < 0 && errno != ENOENT) || r >= (int)sizeof(lbuf)){ 81 | int e = errno; 82 | 83 | diag("Couldn't look up slave %s (%s?)\n",rbuf,strerror(errno)); 84 | errno = e; 85 | return -1; 86 | }else if(r < 0 && errno == ENOENT){ // missing/faulted device 87 | ++degraded; 88 | continue; 89 | } 90 | lbuf[r] = '\0'; 91 | if(strncmp(lbuf,"dev-",4)){ 92 | diag("Couldn't get device from %s\n",lbuf); 93 | return -1; 94 | } 95 | if((c = strdup(lbuf + 4)) == NULL){ 96 | return -1; 97 | } 98 | if((m = malloc(sizeof(*m))) == NULL){ 99 | free(c); 100 | return -1; 101 | } 102 | m->name = c; 103 | m->next = NULL; 104 | *enqm = m; 105 | enqm = &m->next; 106 | lock_growlight(); 107 | if((subd = lookup_device(c)) == NULL){ 108 | unlock_growlight(); 109 | return -1; 110 | } 111 | // m->component = subd; 112 | switch(subd->layout){ 113 | case LAYOUT_NONE: 114 | if(d->mddev.transport == AGGREGATE_UNKNOWN){ 115 | d->mddev.transport = subd->blkdev.transport; 116 | }else if(d->mddev.transport != subd->blkdev.transport){ 117 | d->mddev.transport = AGGREGATE_MIXED; 118 | } 119 | break; 120 | case LAYOUT_MDADM: 121 | if(d->mddev.transport == AGGREGATE_UNKNOWN){ 122 | d->mddev.transport = subd->mddev.transport; 123 | }else if(d->mddev.transport != subd->mddev.transport){ 124 | d->mddev.transport = AGGREGATE_MIXED; 125 | } 126 | break; 127 | case LAYOUT_PARTITION: 128 | if(d->mddev.transport == AGGREGATE_UNKNOWN){ 129 | d->mddev.transport = subd->partdev.parent->blkdev.transport; 130 | }else if(d->mddev.transport != subd->partdev.parent->blkdev.transport){ 131 | d->mddev.transport = AGGREGATE_MIXED; 132 | } 133 | break; 134 | case LAYOUT_ZPOOL: 135 | if(d->mddev.transport == AGGREGATE_UNKNOWN){ 136 | d->mddev.transport = subd->zpool.transport; 137 | }else if(d->mddev.transport != subd->zpool.transport){ 138 | d->mddev.transport = AGGREGATE_MIXED; 139 | } 140 | break; 141 | default: 142 | diag("Unknown layout %d on %s\n",subd->layout,subd->name); 143 | break; 144 | } 145 | unlock_growlight(); 146 | } 147 | d->mddev.degraded = degraded; 148 | if(d->mddev.resync && !d->mddev.degraded){ 149 | // FIXME why do we indicate resync as "1-degraded"? 150 | // don't overload this 151 | d->mddev.degraded = 1; 152 | } 153 | d->mddev.swidth = 0; 154 | if(d->mddev.level && d->mddev.disks && d->mddev.stride){ 155 | const aggregate_type *agg; 156 | 157 | strcpy(buf,"md"); 158 | strcat(buf,d->mddev.level); 159 | if((agg = get_aggregate(buf)) == NULL){ 160 | diag("Didn't know %s type %s\n",d->name,buf); 161 | }else if(d->mddev.disks < agg->maxfaulted){ 162 | diag("%s didn't have %u disks\n",d->name,agg->maxfaulted); 163 | }else{ 164 | d->mddev.swidth = d->mddev.disks - agg->maxfaulted; 165 | } 166 | } 167 | return 0; 168 | } 169 | 170 | int destroy_mdadm(device *d){ 171 | if(d == NULL){ 172 | diag("Passed a NULL device\n"); 173 | return -1; 174 | } 175 | if(d->layout != LAYOUT_MDADM){ 176 | diag("%s is not an MD device\n",d->name); 177 | return -1; 178 | } 179 | if(vspopen_drain("mdadm --stop /dev/%s",d->name)){ 180 | return -1; 181 | } 182 | return 0; 183 | } 184 | 185 | static int 186 | generic_mdadm_create(const char *name,const char *metadata,const char *level, 187 | char * const *comps,int num,int bitmap){ 188 | char buf[BUFSIZ] = ""; 189 | size_t pos; 190 | int z; 191 | 192 | pos = 0; 193 | #define PREFIX "/dev/" 194 | for(z = 0 ; z < num ; ++z){ 195 | if((unsigned)snprintf(buf + pos,sizeof(buf) - pos," %s%s", 196 | strcmp(*comps,"missing") ? "/dev/" : "", 197 | *comps) >= sizeof(buf) - pos){ 198 | diag("Too many arguments for MD creation\n"); 199 | return -1; 200 | } 201 | ++comps; 202 | pos += strlen(buf + pos); 203 | } 204 | #undef PREFIX 205 | // FIXME provide a way to let user control write intent bitmap 206 | return vspopen_drain("mdadm -C \"%s\" --auto=md -e %s -l %s -N \"%s\" -n %d%s%s", 207 | name,metadata,level,name,num, 208 | bitmap ? " -b internal" : "",buf); 209 | } 210 | 211 | int make_mdraid0(const char *name,char * const *comps,int num){ 212 | return generic_mdadm_create(name,"1.2","raid0",comps,num,0); 213 | } 214 | 215 | int make_mdraid1(const char *name,char * const *comps,int num){ 216 | return generic_mdadm_create(name,"1.2","raid1",comps,num,1); 217 | } 218 | 219 | int make_mdraid4(const char *name,char * const *comps,int num){ 220 | return generic_mdadm_create(name,"1.2","raid4",comps,num,1); 221 | } 222 | 223 | int make_mdraid5(const char *name,char * const *comps,int num){ 224 | return generic_mdadm_create(name,"1.2","raid5",comps,num,1); 225 | } 226 | 227 | int make_mdraid6(const char *name,char * const *comps,int num){ 228 | return generic_mdadm_create(name,"1.2","raid6",comps,num,1); 229 | } 230 | 231 | int make_mdraid10(const char *name,char * const *comps,int num){ 232 | return generic_mdadm_create(name,"1.2","raid10",comps,num,1); 233 | } 234 | -------------------------------------------------------------------------------- /src/mdadm.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_MDADM 3 | #define GROWLIGHT_MDADM 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | 11 | // Wants a dirfd corresponding to the md/ sysfs directory for the node 12 | int explore_md_sysfs(struct device *,int); 13 | 14 | int destroy_mdadm(struct device *); 15 | 16 | int make_mdraid0(const char *name,char * const *,int); 17 | int make_mdraid1(const char *name,char * const *,int); 18 | int make_mdraid4(const char *name,char * const *,int); 19 | int make_mdraid5(const char *name,char * const *,int); 20 | int make_mdraid6(const char *name,char * const *,int); 21 | int make_mdraid10(const char *name,char * const *,int); 22 | 23 | #ifdef __cplusplus 24 | } 25 | #endif 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /src/mmap.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "mmap.h" 12 | #include "growlight.h" 13 | 14 | // Handle files which aren't easily supported by mmap(), such as /proc entries 15 | // which don't return their true lengths to fstat() and friends. fd should be 16 | // positioned at the beginning of the file. We use anonymous mappings rather 17 | // than malloc() so that we can pass everything back to munmap(). 18 | static void * 19 | read_map_virt_fd(int fd, off_t *len){ 20 | int pgsize = getpagesize(); 21 | char *buf = malloc(pgsize); 22 | void *map = MAP_FAILED; 23 | ssize_t r; 24 | *len = 0; 25 | 26 | if(pgsize <= 0){ 27 | diag("Invalid pagesize: %d (%s?)\n", pgsize, strerror(errno)); 28 | free(buf); 29 | return MAP_FAILED; 30 | } 31 | size_t mapsize = 0; 32 | while((r = read(fd, buf, sizeof(buf))) > 0){ 33 | size_t size = (*len + r + (pgsize - 1)) / pgsize * pgsize; 34 | if(map == MAP_FAILED){ 35 | map = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); 36 | }else if(mapsize != size){ 37 | void* tmp = mremap(map, mapsize, size, MREMAP_MAYMOVE); 38 | if(tmp == MAP_FAILED){ 39 | munmap(map, mapsize); 40 | } 41 | map = tmp; 42 | } 43 | if(map == MAP_FAILED){ 44 | goto maperr; 45 | } 46 | mapsize = size; 47 | //fprintf(stderr, "read buf: [%s] r: %zd len: %d\n", buf, r, (int)*len); 48 | memcpy((char *)map + *len, buf, r); 49 | *len += r; 50 | //fprintf(stderr, "***[%.*s]***\n", (int)*len, (char*)map); 51 | } 52 | if(r < 0){ 53 | int e = errno; 54 | diag("Error reading %d (%s?)\n", fd, strerror(errno)); 55 | if(map != MAP_FAILED){ 56 | munmap(map, mapsize); 57 | } 58 | errno = e; 59 | free(buf); 60 | return MAP_FAILED; 61 | } 62 | if(*len == 0){ 63 | *len = pgsize; 64 | map = mmap(NULL, *len, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); 65 | if(map == MAP_FAILED){ 66 | goto maperr; 67 | } 68 | } 69 | //fprintf(stderr, "***[%.*s]***\n", (int)*len, (char*)map); 70 | free(buf); 71 | return map; 72 | 73 | maperr: 74 | diag("Couldn't extend map of %d past %ju (%s?)\n", fd, (uintmax_t)*len, strerror(errno)); 75 | *len = -1; 76 | free(buf); 77 | return MAP_FAILED; 78 | } 79 | 80 | void *map_virt_fd(int fd, off_t *len){ 81 | struct stat st; 82 | void *map; 83 | 84 | //fprintf(stderr, "fd %d %d\n", fd, (int)*len); 85 | if(fstat(fd, &st)){ 86 | diag("Couldn't get size of fd %d (%s?)\n", fd, strerror(errno)); 87 | return MAP_FAILED; 88 | } 89 | // Most /proc entries return a 0 length 90 | if((*len = st.st_size) == 0){ 91 | return read_map_virt_fd(fd, len); 92 | } 93 | //fprintf(stderr, "READ MAP MMaP, %d\n", (int)*len); 94 | if((map = mmap(NULL, *len, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED){ 95 | int e = errno; 96 | diag("Couldn't map %ju at %d (%s?)\n", 97 | (uintmax_t)*len, fd, strerror(errno)); 98 | errno = e; 99 | return MAP_FAILED; 100 | } 101 | return map; 102 | } 103 | 104 | void *map_virt_file(const char *fn, int *fd, off_t *len){ 105 | void *map; 106 | int tfd; 107 | 108 | //fprintf(stderr, "OPENING [%s]\n", fn); 109 | if((tfd = open(fn, O_RDONLY|O_NONBLOCK|O_CLOEXEC)) < 0){ 110 | int e = errno; 111 | diag("Couldn't open %s (%s?)\n", fn, strerror(errno)); 112 | errno = e; 113 | return MAP_FAILED; 114 | } 115 | if((map = map_virt_fd(tfd, len)) == MAP_FAILED){ 116 | close(tfd); 117 | return MAP_FAILED; 118 | } 119 | *fd = tfd; 120 | return map; 121 | } 122 | 123 | int munmap_virt(void *map, off_t len){ 124 | return munmap(map, len); 125 | } 126 | -------------------------------------------------------------------------------- /src/mmap.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_MMAP 3 | #define GROWLIGHT_MMAP 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | 11 | void *map_virt_fd(int,off_t *); 12 | void *map_virt_file(const char *,int *,off_t *); 13 | int munmap_virt(void *,off_t); 14 | 15 | #ifdef __cplusplus 16 | } 17 | #endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/mounts.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "fs.h" 15 | #include "zfs.h" 16 | #include "mmap.h" 17 | #include "mounts.h" 18 | #include "growlight.h" 19 | #include "aggregate.h" 20 | 21 | static int 22 | make_parent_directories(const char *path){ 23 | char dir[PATH_MAX + 1]; 24 | char *next; 25 | 26 | assert(strlen(path) < sizeof(dir)); 27 | strcpy(dir,path); 28 | next = dir; 29 | while(*next && (next = strchr(next,'/')) ){ 30 | if(next == dir){ 31 | ++next; 32 | continue; 33 | } 34 | *next = '\0'; 35 | if(mkdir(dir, 0755) && errno != EEXIST){ 36 | diag("Couldn't create directory at %s (%s?)\n", dir, strerror(errno)); 37 | return -1; 38 | } 39 | *next = '/'; 40 | ++next; 41 | } 42 | if(mkdir(dir, 0755) && errno != EEXIST){ 43 | diag("Couldn't create directory at %s (%s?)\n", dir, strerror(errno)); 44 | return -1; 45 | } 46 | return 0; 47 | } 48 | 49 | static int 50 | parse_mount(const char *map, off_t len, char **dev, char **mnt, char **fs, char **ops){ 51 | const char *t; 52 | int r = 0; 53 | 54 | *dev = *mnt = *fs = *ops = NULL; 55 | t = map; 56 | if(len <= t - map){ 57 | goto err; 58 | } 59 | while(isgraph(map[r])){ 60 | if(++r >= len){ 61 | goto err; 62 | } 63 | } 64 | if(!isspace(map[r])){ 65 | goto err; 66 | } 67 | if(r == t - map){ 68 | goto err; 69 | } 70 | if((*dev = strndup(t,r - (t - map))) == NULL){ 71 | goto err; 72 | } 73 | t = map + ++r; 74 | if(len <= t - map){ 75 | goto err; 76 | } 77 | while(isgraph(map[r])){ 78 | if(++r >= len){ 79 | goto err; 80 | } 81 | } 82 | if(!isspace(map[r])){ 83 | goto err; 84 | } 85 | if(r == t - map){ 86 | goto err; 87 | } 88 | if((*mnt = strndup(t,r - (t - map))) == NULL){ 89 | goto err; 90 | } 91 | t = map + ++r; 92 | if(len <= t - map){ 93 | goto err; 94 | } 95 | while(isgraph(map[r])){ 96 | if(++r >= len){ 97 | goto err; 98 | } 99 | } 100 | if(!isspace(map[r])){ 101 | goto err; 102 | } 103 | if(r == t - map){ 104 | goto err; 105 | } 106 | if((*fs = strndup(t,r - (t - map))) == NULL){ 107 | goto err; 108 | } 109 | t = map + ++r; 110 | if(len <= t - map){ 111 | goto err; 112 | } 113 | while(isgraph(map[r])){ 114 | if(++r >= len){ 115 | goto err; 116 | } 117 | } 118 | if(!isspace(map[r])){ 119 | goto err; 120 | } 121 | if(r == t - map){ 122 | goto err; 123 | } 124 | if((*ops = strndup(t,r - (t - map))) == NULL){ 125 | goto err; 126 | } 127 | while(r < len){ 128 | if(map[r] == '\n'){ 129 | break; 130 | } 131 | ++r; 132 | } 133 | if(r >= len){ 134 | goto err; 135 | } 136 | ++r; 137 | return r; 138 | 139 | err: 140 | diag("Couldn't extract mount info from %s\n", map); 141 | free(*dev); 142 | free(*mnt); 143 | free(*fs); 144 | free(*ops); 145 | return -1; 146 | } 147 | 148 | static int 149 | handle_mount(const glightui *gui, const char* mnt, const char* dev, const char* ops, char** fs){ 150 | struct statvfs vfs; 151 | device *d; 152 | 153 | if(statvfs(mnt, &vfs)){ 154 | int skip = 0; 155 | 156 | // We might have mounted a new target atop or above an 157 | // already existing one, in which case we'll need 158 | // possibly recreate the directory structure on the 159 | // newly-mounted filesystem. 160 | if(growlight_target){ 161 | if(strncmp(mnt, growlight_target, strlen(growlight_target)) == 0){ 162 | if(make_parent_directories(mnt) == 0){ 163 | skip = 1; 164 | } // FIXME else remount? otherwise writes 165 | // go to new filesystem rather than old...? 166 | } 167 | } 168 | if(!skip){ 169 | diag("Couldn't stat fs %s (%s?)\n", mnt, strerror(errno)); 170 | return 0; 171 | } 172 | } 173 | if(*dev != '/'){ // have to get zfs's etc 174 | if(fstype_virt_p(*fs)){ 175 | return 0; 176 | } 177 | if((d = lookup_device(dev)) == NULL){ 178 | verbf("virtfs %s at %s\n", *fs, mnt); 179 | return 0; 180 | } 181 | }else{ 182 | const char *rp = dev; 183 | struct stat st; 184 | if(lstat(rp, &st) == 0){ 185 | if(S_ISLNK(st.st_mode)){ 186 | char buf[PATH_MAX + 1]; 187 | int r; 188 | if((r = readlink(dev, buf, sizeof(buf))) < 0){ 189 | diag("Couldn't deref %s (%s?)\n", dev, strerror(errno)); 190 | return 0; 191 | } 192 | if((size_t)r >= sizeof(buf)){ 193 | diag("Name too long for %s (%d?)\n", dev, r); 194 | return 0; 195 | } 196 | buf[r] = '\0'; 197 | rp = buf; 198 | } 199 | } 200 | if((d = lookup_device(rp)) == NULL){ 201 | return 0; 202 | } 203 | } 204 | if(d->mnttype && strcmp(d->mnttype, *fs)){ 205 | diag("Already had mounttype for %s: %s (got %s)\n", 206 | d->name, d->mnttype, *fs); 207 | free(d->mnttype); 208 | d->mnttype = NULL; 209 | free_stringlist(&d->mntops); 210 | free_stringlist(&d->mnt); 211 | d->mnttype = *fs; 212 | *fs = NULL; 213 | } 214 | fs = NULL; 215 | if(add_string(&d->mnt, mnt)){ 216 | return -1; 217 | } 218 | if(add_string(&d->mntops, ops)){ 219 | return -1; 220 | } 221 | d->mntsize = (uintmax_t)vfs.f_bsize * vfs.f_blocks; 222 | if(d->layout == LAYOUT_PARTITION){ 223 | d = d->partdev.parent; 224 | } 225 | d->uistate = gui->block_event(d, d->uistate); 226 | if(growlight_target){ 227 | if(strcmp(mnt, growlight_target) == 0){ 228 | mount_target(); 229 | } 230 | } 231 | return 0; 232 | } 233 | 234 | int parse_mounts(const glightui *gui, const char *fn){ 235 | off_t len, idx; 236 | char *map; 237 | int fd; 238 | 239 | if((map = map_virt_file(fn, &fd, &len)) == MAP_FAILED){ 240 | return -1; 241 | } 242 | idx = 0; 243 | int ret = 0; 244 | while(idx < len){ 245 | char *mnt, *dev, *ops, *fs; 246 | int r; 247 | 248 | dev = mnt = fs = ops = NULL; 249 | if((r = parse_mount(map + idx, len - idx, &dev, &mnt, &fs, &ops)) < 0){ 250 | ret = -1; 251 | break; 252 | } 253 | idx += r; 254 | if(handle_mount(gui, mnt, dev, ops, &fs)){ 255 | ret = -1; // don't exit out of loop 256 | } 257 | free(dev); free(mnt); free(fs); free(ops); 258 | } 259 | munmap_virt(map, len); 260 | close(fd); 261 | return ret; 262 | } 263 | 264 | int mmount(device *d, const char *targ, unsigned mntops, const void *data){ 265 | char name[PATH_MAX + 1]; 266 | char *rname; 267 | 268 | if(d == NULL || targ == NULL){ // mntops may be NULL 269 | diag("Provided NULL arguments\n"); 270 | return -1; 271 | } 272 | if(!d->mnttype){ 273 | diag("%s does not have a filesystem signature\n", d->name); 274 | return -1; 275 | } 276 | if(strcmp(d->mnttype, "zfs") == 0){ 277 | return mount_zfs(d, targ, mntops, data); 278 | } 279 | if(mnttype_aggregablep(d->mnttype)){ 280 | diag("not a mountable filesystem: %s \n", d->mnttype); 281 | return -1; 282 | } 283 | if(growlight_target){ 284 | if(strncmp(targ, growlight_target, strlen(growlight_target)) == 0){ 285 | if(make_parent_directories(targ)){ 286 | diag("Couldn't make parents of %s\n", targ); 287 | } 288 | } 289 | } 290 | if((rname = realpath(targ, NULL)) == NULL){ 291 | diag("Couldn't canonicalize %s (%s)\n", targ, strerror(errno)); 292 | return -1; 293 | } 294 | if(string_included_p(&d->mnt, rname)){ 295 | diag("%s is already mounted at %s\n", d->name, targ); 296 | free(rname); 297 | return -1; 298 | } 299 | if(growlight_target){ 300 | if(strncmp(rname, growlight_target, strlen(growlight_target)) == 0){ 301 | if(make_parent_directories(rname)){ 302 | diag("Couldn't make parents of %s\n", rname); 303 | } 304 | } 305 | } 306 | snprintf(name, sizeof(name), "/dev/%s", d->name); 307 | // Use the original path for the actual mount 308 | if(mount(name, targ, d->mnttype, mntops, data)){ 309 | diag("Error mounting %s (%u) at %s (%s?)\n", 310 | name, mntops, targ, strerror(errno)); 311 | free(rname); 312 | return -1; 313 | } 314 | diag("Mounted %s at %s\n", d->name, targ); 315 | free(rname); 316 | return 0; 317 | } 318 | 319 | int unmount(device *d, const char *path){ 320 | unsigned z; 321 | 322 | if(d->mnt.count == 0){ 323 | diag("%s is not mounted\n", d->name); 324 | return -1; 325 | } 326 | for(z = 0 ; z < d->mnt.count ; ++z){ 327 | if(path && strcmp(d->mnt.list[z], path) == 0){ 328 | continue; 329 | } 330 | diag("Unmounting %s from %s\n", d->name, d->mnt.list[z]); 331 | if(growlight_target){ 332 | if(strcmp(d->mnt.list[z], growlight_target) == 0){ 333 | unmount_target(); 334 | } 335 | } 336 | if(umount2(d->mnt.list[z], UMOUNT_NOFOLLOW)){ 337 | diag("Error unmounting %s at %s (%s?)\n", 338 | d->name, d->mnt.list[z], strerror(errno)); 339 | return -1; 340 | } 341 | } 342 | return 0; 343 | } 344 | 345 | void clear_mounts(controller *c){ 346 | unmount_target(); 347 | while(c){ 348 | device *d; 349 | 350 | for(d = c->blockdevs ; d ; d = d->next){ 351 | device *p; 352 | 353 | // Don't free mnttype. There's still a filesystem. 354 | free_stringlist(&d->mnt); 355 | free_stringlist(&d->mntops); 356 | for(p = d->parts ; p ; p = p->next){ 357 | free_stringlist(&p->mnt); 358 | free_stringlist(&p->mntops); 359 | } 360 | } 361 | c = c->next; 362 | } 363 | } 364 | 365 | unsigned flag_for_mountop(const char *op){ 366 | struct opmap { 367 | const char *o; 368 | unsigned v; 369 | } map[] = { 370 | { 371 | .o = "ro", 372 | .v = MS_RDONLY, 373 | },{ 374 | .o = "dirsync", 375 | .v = MS_DIRSYNC, 376 | },{ 377 | .o = "mand", 378 | .v = MS_MANDLOCK, 379 | },{ 380 | .o = "noatime", 381 | .v = MS_NOATIME, 382 | },{ 383 | .o = "nodev", 384 | .v = MS_NODEV, 385 | },{ 386 | .o = "nodiratime", 387 | .v = MS_NODIRATIME, 388 | },{ 389 | .o = "noexec", 390 | .v = MS_NOEXEC, 391 | },{ 392 | .o = "nosuid", 393 | .v = MS_NOSUID, 394 | },{ 395 | .o = "ro", 396 | .v = MS_RDONLY, 397 | },{ 398 | .o = "relatime", 399 | .v = MS_RELATIME, 400 | },{ 401 | .o = "silent", 402 | .v = MS_SILENT, 403 | },{ 404 | .o = "strictatime", 405 | .v = MS_STRICTATIME, 406 | },{ 407 | .o = "sync", 408 | .v = MS_SYNCHRONOUS, 409 | },{ 410 | .o = NULL, 411 | .v = 0, 412 | } 413 | },*cur; 414 | 415 | for(cur = map ; cur->o ; ++cur){ 416 | if(strcmp(cur->o,op) == 0){ 417 | return cur->v; 418 | } 419 | } 420 | return 0; 421 | } 422 | -------------------------------------------------------------------------------- /src/mounts.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_MOUNTS 3 | #define GROWLIGHT_MOUNTS 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | struct controller; 11 | struct growlight_ui; 12 | 13 | // (Re)parse the specified file having /proc/mounts format. Remember that 14 | // /proc/mounts must be poll()ed with POLLPRI, not POLLIN! 15 | int parse_mounts(const struct growlight_ui *,const char *); 16 | int mmount(struct device *,const char *,unsigned,const void *); 17 | int unmount(struct device *,const char *); 18 | void clear_mounts(struct controller *); 19 | unsigned flag_for_mountop(const char *); 20 | 21 | #ifdef __cplusplus 22 | } 23 | #endif 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/msdos.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "mbr.h" 12 | #include "msdos.h" 13 | #include "ptypes.h" 14 | #include "ptable.h" 15 | #include "growlight.h" 16 | 17 | #define LBA_SIZE 512u 18 | #define MBR_SIZE (LBA_SIZE - MBR_OFFSET) 19 | #define DISKSIG_LEN 4 20 | #define MSDOS_ENTRIES 4 21 | 22 | static const uint8_t MBR_SIG[2] = { 0x55, 0xaa }; 23 | 24 | // 16-byte msdos partition entry. A msdos table provides space for 4 of these, 25 | // but they can be extended (subpartitioned). 26 | typedef struct __attribute__ ((packed)) msdos_entry { 27 | uint8_t flags; 28 | uint8_t hstart, sstart, cstart; // 8-6-10 layout (8)-(2/6)-(8) 29 | uint8_t ptype; 30 | uint8_t hlast, slast, clast; // 8-6-10 layout (8)-(2/6)-(8) 31 | uint32_t lbafirst; // little-endian 32 | uint32_t lbasect; // little-endian 33 | } msdos_entry; 34 | 35 | // One LBA block, padded with zeroes at the end. 72 bytes, offset by 440. 36 | typedef struct __attribute__ ((packed)) msdos_header { 37 | uint8_t bootstrap[MBR_OFFSET]; 38 | unsigned char disksig[DISKSIG_LEN]; 39 | uint16_t reserved; 40 | msdos_entry table[MSDOS_ENTRIES]; 41 | uint16_t bootsig; 42 | } msdos_header; 43 | 44 | static int 45 | initialize_msdos(msdos_header *mh){ 46 | if(getrandom(mh->disksig, sizeof(mh->disksig), GRND_NONBLOCK) != sizeof(mh->disksig)){ 47 | diag("Couldn't get %zu random bytes (%s)\n", sizeof(mh->disksig), strerror(errno)); 48 | return -1; 49 | } 50 | memset(&mh->reserved, 0, sizeof(mh->reserved)); 51 | memset(&mh->table, 0, sizeof(mh->table)); 52 | memcpy(&mh->bootsig, MBR_SIG, sizeof(mh->bootsig)); 53 | return 0; 54 | } 55 | 56 | // Write out a msdos partition map on the device represented by fd, using 57 | // lbasize-byte LBA. We will write to the first sector only. We can either zero 58 | // it all out, or create a new empty msdos. Set realdata not equal to 0 to 59 | // perform the latter. 60 | static int 61 | write_msdos(int fd, ssize_t lbasize, unsigned realdata){ 62 | int pgsize = getpagesize(); 63 | msdos_header *mhead; 64 | size_t mapsize; 65 | void *map; 66 | 67 | assert(pgsize > 0 && pgsize % lbasize == 0); 68 | mapsize = lbasize; 69 | mapsize = ((mapsize / pgsize) + !!(mapsize % pgsize)) * pgsize; 70 | assert(mapsize % pgsize == 0 && mapsize); 71 | map = mmap(NULL, mapsize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 72 | if(map == MAP_FAILED){ 73 | diag("Error mapping %zub at %d (%s?)\n", mapsize, fd, strerror(errno)); 74 | return -1; 75 | } 76 | mhead = (msdos_header *)map; 77 | if(!realdata){ 78 | memset(mhead, 0, MBR_OFFSET + MBR_SIZE); 79 | }else{ 80 | if(initialize_msdos(mhead)){ 81 | munmap(map, mapsize); 82 | return -1; 83 | } 84 | } 85 | if(msync(map, mapsize, MS_SYNC|MS_INVALIDATE)){ 86 | diag("Error syncing %d (%s?)\n", fd, strerror(errno)); 87 | munmap(map, mapsize); 88 | return -1; 89 | } 90 | if(munmap(map, mapsize)){ 91 | diag("Error unmapping %d (%s?)\n", fd, strerror(errno)); 92 | return -1; 93 | } 94 | return 0; 95 | } 96 | 97 | int new_msdos(device *d){ 98 | int fd; 99 | 100 | if(d->layout != LAYOUT_NONE){ 101 | diag("Won't create partition table on non-disk %s\n", d->name); 102 | return -1; 103 | } 104 | if(d->size % LBA_SIZE){ 105 | diag("Won't create msdos on (%ju %% %u == %juB) disk %s\n", 106 | d->size, LBA_SIZE, d->size % LBA_SIZE, d->name); 107 | return -1; 108 | } 109 | if(d->size < LBA_SIZE){ 110 | diag("Won't create msdos on empty disk %s\n", d->name); 111 | return -1; 112 | } 113 | if((fd = openat(devfd, d->name, O_RDWR|O_CLOEXEC|O_DIRECT)) < 0){ 114 | diag("Couldn't open %s (%s?)\n", d->name, strerror(errno)); 115 | return -1; 116 | } 117 | if(write_msdos(fd, LBA_SIZE, 1)){ 118 | diag("Couldn't write msdos on %s (%s?)\n", d->name, strerror(errno)); 119 | close(fd); 120 | return -1; 121 | } 122 | if(fsync(fd)){ 123 | diag("Warning: error syncing %d for %s (%s?)\n", fd, d->name, strerror(errno)); 124 | } 125 | if(close(fd)){ 126 | diag("Error closing %d for %s (%s?)\n", fd, d->name, strerror(errno)); 127 | return -1; 128 | } 129 | return 0; 130 | } 131 | 132 | int zap_msdos(device *d){ 133 | if(d->layout != LAYOUT_NONE){ 134 | diag("Won't zap partition table on non-disk %s\n", d->name); 135 | return -1; 136 | } 137 | if(d->blkdev.pttable == NULL || strcmp(d->blkdev.pttable, "dos")){ 138 | diag("No msdos on disk %s\n", d->name); 139 | return -1; 140 | } 141 | return wipe_dos_ptable(d); 142 | } 143 | 144 | // Map the primary msdos header, its table, and the MBR boot sector. 145 | static void * 146 | map_msdos(device *d, size_t *mapsize, int *fd, size_t lbasize){ 147 | const int pgsize = getpagesize(); 148 | void *map; 149 | 150 | if(pgsize < 0){ 151 | diag("Bad pgsize for msdos: %d\n", pgsize); 152 | return MAP_FAILED; 153 | } 154 | if((*fd = openat(devfd, d->name, O_RDWR|O_CLOEXEC|O_DIRECT)) < 0){ 155 | diag("Couldn't open %s (%s?)\n", d->name, strerror(errno)); 156 | return MAP_FAILED; 157 | } 158 | *mapsize = lbasize; 159 | *mapsize = ((*mapsize / pgsize) + !!(*mapsize % pgsize)) * pgsize; 160 | assert(*mapsize % pgsize == 0); 161 | map = mmap(NULL, *mapsize, PROT_READ|PROT_WRITE, MAP_SHARED, *fd, 0); 162 | if(map == MAP_FAILED){ 163 | diag("Couldn't map msdos header (%s?)\n", strerror(errno)); 164 | close(*fd); 165 | return map; 166 | } 167 | return map; 168 | } 169 | 170 | // Pass the return from map_msdos(), ie the MBR boot sector 171 | static int 172 | unmap_msdos(const device *parent, void *map, size_t mapsize, int fd){ 173 | const int pgsize = getpagesize(); 174 | 175 | assert(parent->layout == LAYOUT_NONE); 176 | if(pgsize < 0){ 177 | diag("Warning: bad pgsize for msdos: %d\n", pgsize); 178 | } 179 | if(munmap(map, mapsize)){ 180 | int e = errno; 181 | 182 | diag("Error munmapping %s (%s?)\n", parent->name, strerror(errno)); 183 | close(fd); 184 | errno = e; 185 | return -1; 186 | } 187 | return 0; 188 | } 189 | 190 | int add_msdos(device *d, const wchar_t *name, uintmax_t fsec, uintmax_t lsec, unsigned long long code){ 191 | static unsigned char zmpe[16] = ""; 192 | const size_t lbasize = LBA_SIZE; 193 | unsigned z, partno; 194 | msdos_entry *mpe; 195 | unsigned mbrcode; 196 | size_t mapsize; 197 | uint64_t lbas; 198 | void *map; 199 | int fd, r; 200 | 201 | if(name){ 202 | diag("msdos partitions don't support names\n"); 203 | return -1; 204 | } 205 | if((lsec - fsec) * d->logsec > 2ull * 1000ull * 1000ull * 1000ull * 1000ull){ 206 | diag("msdos partitions may not exceed 2TB\n"); 207 | return -1; 208 | } 209 | if(!d){ 210 | diag("Passed a NULL device\n"); 211 | return -1; 212 | } 213 | if(get_mbr_code(code, &mbrcode)){ 214 | diag("Illegal code for DOS/BIOS/MBR: %llu\n", code); 215 | return -1; 216 | } 217 | if(d->layout != LAYOUT_NONE){ 218 | diag("Won't add partition to non-disk %s\n", d->name); 219 | return -1; 220 | } 221 | if(d->blkdev.pttable == NULL || strcmp(d->blkdev.pttable, "dos")){ 222 | diag("No msdos on disk %s\n", d->name); 223 | return -1; 224 | } 225 | if(d->size % lbasize){ 226 | diag("Disk size is not a multiple of LBA size, aborting\n"); 227 | return -1; 228 | } 229 | lbas = d->size / lbasize; 230 | // Align it properly 231 | if(fsec % (d->physsec / d->logsec)){ 232 | fsec += (d->physsec / d->logsec) - (fsec % (d->physsec / d->logsec)); 233 | assert(fsec % (d->physsec / d->logsec) == 0); 234 | } 235 | if(lsec < fsec || lsec > last_usable_sector(d) || fsec < first_usable_sector(d)){ 236 | diag("Bad sector spec (%ju:%ju) on %ju disk\n", fsec, lsec, lbas); 237 | return -1; 238 | } 239 | if((map = map_msdos(d, &mapsize, &fd, LBA_SIZE)) == MAP_FAILED){ 240 | return -1; 241 | } 242 | mpe = (msdos_entry *)((char *)map + MBR_OFFSET + 6); 243 | // Determine the next available partition number, and verify that no 244 | // existing partitions overlap with this one. 245 | partno = MSDOS_ENTRIES; 246 | for(z = 0 ; z < MSDOS_ENTRIES ; ++z){ 247 | // if there're any non-zero bits, assume it's being used. 248 | if(memcmp(&mpe[z], zmpe, sizeof(zmpe))){ 249 | if((mpe[z].lbafirst >= fsec && mpe[z].lbafirst <= lsec) || 250 | (mpe[z].lbafirst + mpe[z].lbasect - 1 <= lsec && mpe[z].lbafirst + mpe[z].lbasect - 1 >= fsec)){ 251 | diag("Partition overlap (%ju:%ju) ([%u]%u:%u)\n", fsec, lsec, 252 | z, mpe[z].lbafirst, mpe[z].lbafirst + mpe[z].lbasect - 1); 253 | } 254 | continue; 255 | } 256 | if(partno == MSDOS_ENTRIES){ 257 | partno = z; 258 | } 259 | } 260 | if((z = partno) == MSDOS_ENTRIES){ 261 | diag("no entry for a new partition in %s\n", d->name); 262 | munmap(map, mapsize); 263 | close(fd); 264 | return -1; 265 | } 266 | diag("First sector: %ju last sector: %ju count: %ju size: %ju\n", 267 | (uintmax_t)fsec, 268 | (uintmax_t)lsec, 269 | (uintmax_t)(lsec - fsec), 270 | (uintmax_t)((lsec - fsec) * d->logsec)); 271 | memset(&mpe[z], 0, sizeof(*mpe)); 272 | mpe[z].ptype = mbrcode; 273 | mpe[z].lbafirst = fsec; 274 | mpe[z].lbasect = lsec - fsec + 1; 275 | if(unmap_msdos(d, map, mapsize, fd)){ 276 | close(fd); 277 | return -1; 278 | } 279 | if(fsync(fd)){ 280 | diag("Couldn't sync %d for %s\n", fd, d->name); 281 | } 282 | r = blkpg_add_partition(fd, fsec * LBA_SIZE, 283 | (lsec - fsec + 1) * LBA_SIZE, z + 1, ""); 284 | if(close(fd)){ 285 | int e = errno; 286 | 287 | diag("Error closing %s (%s?)\n", d->name, strerror(errno)); 288 | errno = e; 289 | return -1; 290 | } 291 | return r; 292 | } 293 | 294 | int flags_msdos(device *d, uint64_t flags){ 295 | msdos_entry *mpe; 296 | size_t mapsize; 297 | unsigned g; 298 | void *map; 299 | int fd; 300 | 301 | if(flags != 0x80 && flags != 0){ 302 | diag("Invalid flags for BIOS/MBR: 0x%016jx\n", (uintmax_t)flags); 303 | return -1; 304 | } 305 | assert(d->layout == LAYOUT_PARTITION); 306 | if(d->partdev.ptype != PARTROLE_PRIMARY || d->partdev.ptstate.logical || d->partdev.ptstate.extended){ 307 | diag("Flags are only set on primary partitions\n"); 308 | return -1; 309 | } 310 | if(d->partdev.pnumber == 0 || d->partdev.pnumber > MSDOS_ENTRIES){ 311 | diag("No support for partnumber %u\n", d->partdev.pnumber); 312 | return -1; 313 | } 314 | g = d->partdev.pnumber - 1; 315 | if((map = map_msdos(d->partdev.parent, &mapsize, &fd, LBA_SIZE)) == MAP_FAILED){ 316 | return -1; 317 | } 318 | mpe = (msdos_entry *)((char *)map + MBR_OFFSET + 6); 319 | mpe[g].flags = flags; 320 | if(unmap_msdos(d->partdev.parent, map, mapsize, fd)){ 321 | close(fd); 322 | return -1; 323 | } 324 | if(close(fd)){ 325 | diag("Error closing %s (%s?)\n", d->name, strerror(errno)); 326 | return -1; 327 | } 328 | return 0; 329 | } 330 | 331 | int flag_msdos(device *d, uint64_t flag, unsigned status){ 332 | msdos_entry *mpe; 333 | size_t mapsize; 334 | unsigned g; 335 | void *map; 336 | int fd; 337 | 338 | if(flag != 0x80){ 339 | diag("Invalid flag for BIOS/MBR: 0x%016jx\n", (uintmax_t)flag); 340 | return -1; 341 | } 342 | assert(d->layout == LAYOUT_PARTITION); 343 | if(d->partdev.ptype != PARTROLE_PRIMARY || d->partdev.ptstate.logical || d->partdev.ptstate.extended){ 344 | diag("Flags are only set on primary partitions\n"); 345 | return -1; 346 | } 347 | if(d->partdev.pnumber == 0 || d->partdev.pnumber > MSDOS_ENTRIES){ 348 | diag("No support for partnumber %u\n", d->partdev.pnumber); 349 | return -1; 350 | } 351 | g = d->partdev.pnumber - 1; 352 | if((map = map_msdos(d->partdev.parent, &mapsize, &fd, LBA_SIZE)) == MAP_FAILED){ 353 | return -1; 354 | } 355 | mpe = (msdos_entry *)((char *)map + MBR_OFFSET + 6); 356 | if(status){ 357 | mpe[g].flags |= flag; 358 | }else{ 359 | mpe[g].flags &= ~flag; 360 | } 361 | if(unmap_msdos(d->partdev.parent, map, mapsize, fd)){ 362 | close(fd); 363 | return -1; 364 | } 365 | if(close(fd)){ 366 | diag("Error closing %s (%s?)\n", d->name, strerror(errno)); 367 | return -1; 368 | } 369 | return 0; 370 | } 371 | 372 | int code_msdos(device *d, unsigned long long code){ 373 | msdos_entry *mpe; 374 | unsigned mbrcode; 375 | size_t mapsize; 376 | unsigned g; 377 | void *map; 378 | int fd; 379 | 380 | if(get_mbr_code(code, &mbrcode)){ 381 | diag("Illegal code for DOS/BIOS/MBR: %llu\n", code); 382 | return -1; 383 | } 384 | assert(d->layout == LAYOUT_PARTITION); 385 | if(d->partdev.pnumber == 0 || d->partdev.pnumber > MSDOS_ENTRIES){ 386 | diag("No support for partnumber %u\n", d->partdev.pnumber); 387 | return -1; 388 | } 389 | g = d->partdev.pnumber - 1; 390 | if((map = map_msdos(d->partdev.parent, &mapsize, &fd, LBA_SIZE)) == MAP_FAILED){ 391 | return -1; 392 | } 393 | mpe = (msdos_entry *)((char *)map + MBR_OFFSET + 6); 394 | if(mpe[g].lbafirst == 0 || mpe[g].lbasect == 0){ 395 | diag("Not a valid msdos partition: %s\n", d->name); 396 | unmap_msdos(d->partdev.parent, map, mapsize, fd); 397 | close(fd); 398 | return -1; 399 | } 400 | mpe[g].ptype = mbrcode; 401 | if(unmap_msdos(d->partdev.parent, map, mapsize, fd)){ 402 | close(fd); 403 | return -1; 404 | } 405 | if(close(fd)){ 406 | diag("Error closing %s (%s?)\n", d->name, strerror(errno)); 407 | return -1; 408 | } 409 | return 0; 410 | } 411 | 412 | int del_msdos(const device *p){ 413 | msdos_entry *mpe; 414 | size_t mapsize; 415 | unsigned g; 416 | void *map; 417 | int fd, r; 418 | 419 | assert(p->layout == LAYOUT_PARTITION); 420 | if(p->partdev.pnumber == 0 || p->partdev.pnumber > MSDOS_ENTRIES){ 421 | diag("No support for partnumber %u\n", p->partdev.pnumber); 422 | return -1; 423 | } 424 | g = p->partdev.pnumber - 1; 425 | if((map = map_msdos(p->partdev.parent, &mapsize, &fd, LBA_SIZE)) == MAP_FAILED){ 426 | return -1; 427 | } 428 | mpe = (msdos_entry *)((char *)map + MBR_OFFSET + 6); 429 | memset(&mpe[g], 0, sizeof(*mpe)); 430 | if(unmap_msdos(p->partdev.parent, map, mapsize, fd)){ 431 | close(fd); 432 | return -1; 433 | } 434 | if(fsync(fd)){ 435 | diag("Couldn't sync %d for %s\n", fd, p->name); 436 | } 437 | r = blkpg_del_partition(fd, p->partdev.fsector * LBA_SIZE, 438 | p->size, p->partdev.pnumber, 439 | p->partdev.parent->name); 440 | if(close(fd)){ 441 | diag("Couldn't close %s (%s?)\n", p->partdev.parent->name, strerror(errno)); 442 | return -1; 443 | } 444 | return r; 445 | } 446 | 447 | uintmax_t first_msdos(const device *d __attribute__ ((unused))){ 448 | return 1; 449 | } 450 | 451 | uintmax_t last_msdos(const device *d){ 452 | return d->logsec && d->size ? d->size / d->logsec - 1 : 0; 453 | } 454 | -------------------------------------------------------------------------------- /src/msdos.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_MSDOS 3 | #define GROWLIGHT_MSDOS 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | #include 11 | 12 | struct device; 13 | 14 | // Pass the block device 15 | int new_msdos(struct device *); 16 | int zap_msdos(struct device *); 17 | int add_msdos(struct device *,const wchar_t *,uintmax_t,uintmax_t,unsigned long long); 18 | 19 | // Pass the partition 20 | int del_msdos(const struct device *); 21 | int flag_msdos(struct device *,uint64_t,unsigned); 22 | int flags_msdos(struct device *,uint64_t); 23 | int code_msdos(struct device *,unsigned long long); 24 | 25 | uintmax_t first_msdos(const struct device *); 26 | uintmax_t last_msdos(const struct device *); 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /src/notcurses/notcurses-ui.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_SRC_UI_NOTCURSES 3 | #define GROWLIGHT_SRC_UI_NOTCURSES 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct form_option; 10 | struct panel_state; 11 | 12 | void locked_diag(const char *,...); 13 | 14 | // Scrolling single select form 15 | void raise_form(const char *,void (*)(const char *),struct form_option *, 16 | int,int,const char *); 17 | 18 | // Single-entry string entry form with command-line editing 19 | void raise_str_form(const char *,void (*)(const char *), 20 | const char *,const char *); 21 | 22 | // Multiselect form with side panel 23 | void raise_multiform(const char *,void (*)(const char *,char **,int,int), 24 | struct form_option *,int,int,int,char **,int,const char *,int); 25 | 26 | struct panel_state *show_splash(const wchar_t *); 27 | void kill_splash(struct panel_state *); 28 | 29 | #ifdef __cplusplus 30 | } 31 | #endif 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/notcurses/notui-aggregate.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | 6 | #include "growlight.h" 7 | #include "aggregate.h" 8 | #include "notcurses-ui.h" 9 | #include "notui-aggregate.h" 10 | 11 | static const char AGGCOMP_TEXT[] = 12 | "Bind devices to the new aggregate. To be eligible, a device must either be " 13 | "unpartitioned, or be a partition having the appropriate component type. The " 14 | "device furthermore must not have a valid filesystem signature."; 15 | 16 | static const char AGGTYPE_TEXT[] = 17 | "What kind of aggregate do you hope to create?"; 18 | 19 | static const char AGGNAME_TEXT[] = 20 | "The chosen name will be the primary means by which the system makes use of " 21 | "this new aggregate, so choose wisely, and plan for the future!"; 22 | 23 | typedef enum { 24 | FORM_SELECT, // form_option[] 25 | FORM_STRING_INPUT, // form_input 26 | } form_enum; 27 | 28 | struct form_option { 29 | char *option; // option key (the string passed to cb) 30 | char *desc; // longer description 31 | }; 32 | 33 | struct form_input { 34 | const char *prompt; // short prompt. currently aliases boxstr 35 | char *longprompt; // longer prompt, not currently used 36 | char *buffer; // input buffer, initialized to "" 37 | }; 38 | 39 | struct form_state { 40 | struct ncplane* p; 41 | int ysize; // number of lines of *text* (not win) 42 | void (*fxn)(const char *); // callback once form is done 43 | int idx; // selection index, [0..ysize) 44 | int longop; // length of longest op 45 | char *boxstr; // string for box label 46 | form_enum formtype; // type of form 47 | union { 48 | struct { 49 | struct form_option *ops;// form_option array for *this instance* 50 | int scrolloff; // scroll offset 51 | int opcount; // total number of ops 52 | }; 53 | struct form_input inp; // form_input state for this instance 54 | }; 55 | }; 56 | 57 | static char *pending_aggname; 58 | static char *pending_aggtype; 59 | 60 | static void 61 | destroy_agg_forms(void){ 62 | free(pending_aggtype); 63 | pending_aggtype = NULL; 64 | free(pending_aggname); 65 | pending_aggname = NULL; 66 | } 67 | 68 | static struct form_option * 69 | agg_table(int *count,const char *match,int *defidx){ 70 | const aggregate_type *types; 71 | struct form_option *fo; 72 | int z; 73 | 74 | *defidx = -1; 75 | if((types = get_aggregate_types(count)) == NULL){ 76 | return NULL; 77 | } 78 | if((fo = malloc(sizeof(*fo) * *count)) == NULL){ 79 | return NULL; 80 | } 81 | for(z = 0 ; z < *count ; ++z){ 82 | char *key,*desc; 83 | 84 | if((key = strdup(types[z].name)) == NULL){ 85 | goto err; 86 | } 87 | if(match){ 88 | if(strcmp(key,match) == 0){ 89 | *defidx = z; 90 | } 91 | }else{ 92 | if(aggregate_default_p(key)){ 93 | *defidx = z; 94 | } 95 | } 96 | if((desc = strdup(types[z].desc)) == NULL){ 97 | free(key); 98 | goto err; 99 | } 100 | fo[z].option = key; 101 | fo[z].desc = desc; 102 | } 103 | return fo; 104 | 105 | err: 106 | while(z--){ 107 | free(fo[z].option); 108 | free(fo[z].desc); 109 | } 110 | free(fo); 111 | *count = 0; 112 | return NULL; 113 | } 114 | 115 | // On success, we must free input desc (hence it not being const) 116 | static char * 117 | prefix_desc_with_size(const device *d,char *desc){ 118 | char s[NCBPREFIXSTRLEN],*uni; 119 | size_t sz; 120 | 121 | ncbprefix(d->size, 1, s, 1); 122 | // Might be a bit overspacious due to full NCBPREFIXSTRLEN, but who cares 123 | sz = (sizeof(s) / sizeof(*s)) + strlen(desc) + 5; // '(' 'B' ')' ' ' '\0' 124 | if((uni = malloc(sz)) == NULL){ 125 | return NULL; 126 | } 127 | if((unsigned)snprintf(uni,sz,"(%*sB) %s", NCBPREFIXFMT(s), desc) >= sz){ 128 | free(uni); 129 | return NULL; 130 | } 131 | free(desc); 132 | return uni; 133 | } 134 | 135 | static struct form_option * 136 | grow_component_table(const device *d,int *count,const char *match,int *defidx, 137 | char ***selarray,int *selections,struct form_option *fo){ 138 | struct form_option *tmp; 139 | char *key,*desc; 140 | int z; 141 | 142 | if((key = strdup(d->name)) == NULL){ 143 | return NULL; 144 | } 145 | if(match){ 146 | if(strcmp(key,match) == 0){ 147 | *defidx = *count; 148 | for(z = 0 ; z < *selections ; ++z){ 149 | if(strcmp(key,(*selarray)[z]) == 0){ 150 | free((*selarray)[z]); 151 | (*selarray)[z] = NULL; 152 | if(z < *selections - 1){ 153 | memmove(&(*selarray)[z],&(*selarray)[z + 1],sizeof(**selarray) * (*selections - 1 - z)); 154 | } 155 | --*selections; 156 | z = -1; 157 | break; 158 | } 159 | } 160 | if(z >= *selections){ 161 | typeof(*selarray) stmp; 162 | 163 | if((stmp = realloc(*selarray,sizeof(**selarray) * (*selections + 1))) == NULL){ 164 | free(key); 165 | return NULL; 166 | } 167 | *selarray = stmp; 168 | (*selarray)[*selections] = strdup(match); 169 | ++*selections; 170 | } 171 | } 172 | } 173 | if((desc = strdup(d->bypath ? d->bypath : 174 | d->byid ? d->byid : 175 | (d->layout == LAYOUT_NONE && d->blkdev.serial) ? 176 | d->blkdev.serial : 177 | d->name)) == NULL){ 178 | free(key); 179 | return NULL; 180 | } 181 | char* tmpdesc; 182 | // free()s old desc on success 183 | if((tmpdesc = prefix_desc_with_size(d,desc)) == NULL){ 184 | free(desc); 185 | free(key); 186 | return NULL; 187 | } 188 | desc = tmpdesc; 189 | if((tmp = realloc(fo,sizeof(*fo) * (*count + 1))) == NULL){ 190 | free(key); 191 | free(desc); 192 | return NULL; 193 | } 194 | fo = tmp; 195 | for(z = 0 ; z < *count ; ++z){ 196 | if(strcmp(desc,fo[z].desc) > 0){ 197 | memmove(&fo[z + 1],&fo[z],sizeof(*fo) * (*count - z)); 198 | break; 199 | } 200 | } 201 | fo[z].option = key; 202 | fo[z].desc = desc; 203 | ++*count; 204 | return fo; 205 | } 206 | 207 | static struct form_option * 208 | component_table(const aggregate_type *at,int *count,const char *match,int *defidx, 209 | char ***selarray,int *selections){ 210 | struct form_option *fo = NULL,*tmp; 211 | const controller *c; 212 | 213 | *count = 0; 214 | *defidx = -1; 215 | for(c = get_controllers() ; c ; c = c->next){ 216 | const device *d; 217 | 218 | for(d = c->blockdevs ; d ; d = d->next){ 219 | const device *p; 220 | 221 | if(device_aggregablep(d)){ 222 | if((tmp = grow_component_table(d,count,match,defidx,selarray,selections,fo)) == NULL){ 223 | goto err; 224 | } 225 | fo = tmp; 226 | } 227 | for(p = d->parts ; p ; p = p->next){ 228 | if(device_aggregablep(p)){ 229 | if((tmp = grow_component_table(p,count,match,defidx,selarray,selections,fo)) == NULL){ 230 | goto err; 231 | } 232 | fo = tmp; 233 | } 234 | } 235 | } 236 | } 237 | if(at->maxfaulted){ 238 | device fauxd; 239 | 240 | memset(&fauxd,0,sizeof(fauxd)); 241 | strncpy(fauxd.name,"missing",sizeof(fauxd.name)); 242 | fauxd.bypath = "force construction of degraded array"; 243 | if((tmp = grow_component_table(&fauxd,count,match,defidx,selarray,selections,fo)) == NULL){ 244 | goto err; 245 | } 246 | fo = tmp; 247 | } 248 | *defidx = *count ? (*defidx + 1) % *count : -1; 249 | return fo; 250 | 251 | err: 252 | // FIXME free up selarray? 253 | while(*count--){ 254 | free(fo[*count].option); 255 | free(fo[*count].desc); 256 | } 257 | free(fo); 258 | return NULL; 259 | } 260 | 261 | static void agg_callback(const char *); 262 | static void aggname_callback(const char *); 263 | 264 | static void 265 | do_agg(const aggregate_type *at,char * const *selarray,int selections){ 266 | struct panel_state *ps; 267 | int r; 268 | 269 | if(at->makeagg == NULL){ 270 | locked_diag("FIXME %s creation is not yet implemented",pending_aggtype); 271 | return; 272 | } 273 | 274 | ps = show_splash(L"Creating aggregate..."); 275 | r = at->makeagg(pending_aggname,selarray,selections); 276 | if(ps){ 277 | kill_splash(ps); 278 | } 279 | if(r == 0){ 280 | locked_diag("Successfully created %s",pending_aggtype); 281 | } 282 | } 283 | 284 | static void 285 | aggcomp_callback(const char *fn,char **selarray,int selections,int scrollp){ 286 | struct form_option *comps_agg; 287 | const aggregate_type *at; 288 | int opcount,defidx; 289 | 290 | assert(selections >= 0); 291 | if(fn == NULL){ 292 | raise_str_form("enter aggregate name",aggname_callback, 293 | pending_aggname,AGGNAME_TEXT); 294 | return; 295 | } 296 | if((at = get_aggregate(pending_aggtype)) == NULL){ 297 | destroy_agg_forms(); 298 | return; 299 | } 300 | if(strcmp(fn,"") == 0){ 301 | if((unsigned)selections >= at->mindisks){ 302 | do_agg(at,selarray,selections); 303 | destroy_agg_forms(); 304 | return; 305 | } 306 | } 307 | if((comps_agg = component_table(at,&opcount,fn,&defidx,&selarray,&selections)) == NULL){ 308 | struct form_option *ops_agg; 309 | 310 | if( (ops_agg = agg_table(&opcount,pending_aggtype,&defidx)) ){ 311 | raise_form("select an aggregate type",agg_callback,ops_agg, 312 | opcount,defidx,AGGTYPE_TEXT); 313 | }else{ 314 | destroy_agg_forms(); 315 | } 316 | locked_diag("insufficiently many available devices for %s",pending_aggtype); 317 | return; 318 | } 319 | raise_multiform("select aggregate components",aggcomp_callback,comps_agg, 320 | opcount,defidx,at->mindisks,selarray,selections,AGGCOMP_TEXT,scrollp); 321 | if((unsigned)selections < at->mindisks){ 322 | locked_diag("%s needs at least %d devices",pending_aggtype,at->mindisks); 323 | }else{ 324 | locked_diag("%s device requirement (%d) satisfied",pending_aggtype,at->mindisks); 325 | } 326 | } 327 | 328 | static void 329 | aggname_callback(const char *fn){ 330 | struct form_option *comps_agg; 331 | const aggregate_type *at; 332 | int selections = 0; 333 | int opcount,defidx; 334 | char **selarray; 335 | 336 | if(fn == NULL){ 337 | struct form_option *ops_agg; 338 | 339 | if( (ops_agg = agg_table(&opcount,pending_aggtype,&defidx)) ){ 340 | raise_form("select an aggregate type",agg_callback,ops_agg, 341 | opcount,defidx,AGGTYPE_TEXT); 342 | } 343 | return; 344 | } 345 | if((pending_aggname = strdup(fn)) == NULL){ 346 | destroy_agg_forms(); 347 | return; 348 | } 349 | if((at = get_aggregate(pending_aggtype)) == NULL){ 350 | destroy_agg_forms(); 351 | return; 352 | } 353 | selarray = NULL; 354 | if((comps_agg = component_table(at,&opcount,NULL,&defidx,&selarray,&selections)) == NULL){ 355 | struct form_option *ops_agg; 356 | 357 | if( (ops_agg = agg_table(&opcount,pending_aggtype,&defidx)) ){ 358 | raise_form("select an aggregate type",agg_callback,ops_agg, 359 | opcount,defidx,AGGTYPE_TEXT); 360 | }else{ 361 | destroy_agg_forms(); 362 | } 363 | locked_diag("insufficiently many available devices for %s",pending_aggtype); 364 | return; 365 | } 366 | raise_multiform("select aggregate components",aggcomp_callback,comps_agg, 367 | opcount,defidx,at->mindisks,selarray,selections,AGGCOMP_TEXT,0); 368 | } 369 | 370 | static void 371 | agg_callback(const char *fn){ 372 | const aggregate_type *at; 373 | 374 | if(fn == NULL){ 375 | locked_diag("aggregate creation was cancelled"); 376 | return; 377 | } 378 | if((at = get_aggregate(fn)) == NULL){ 379 | destroy_agg_forms(); 380 | return; 381 | } 382 | if((pending_aggtype = strdup(fn)) == NULL){ 383 | destroy_agg_forms(); 384 | return; 385 | } 386 | raise_str_form("enter aggregate name",aggname_callback,at->defname, 387 | AGGNAME_TEXT); 388 | } 389 | 390 | int raise_aggregate_form(void){ 391 | struct form_option *ops_agg; 392 | int opcount, defidx; 393 | 394 | if((ops_agg = agg_table(&opcount, pending_aggtype, &defidx)) == NULL){ 395 | destroy_agg_forms(); 396 | return -1; 397 | } 398 | raise_form("select an aggregate type", agg_callback, ops_agg, opcount, defidx, AGGTYPE_TEXT); 399 | return 0; 400 | } 401 | -------------------------------------------------------------------------------- /src/notcurses/notui-aggregate.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_SRC_NOTUI_AGGREGATE 3 | #define GROWLIGHT_SRC_NOTUI_AGGREGATE 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | int raise_aggregate_form(void); 10 | 11 | #ifdef __cplusplus 12 | } 13 | #endif 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/nvme.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include "sg.h" 3 | #include "nvme.h" 4 | #include 5 | #include 6 | #include 7 | #include "growlight.h" 8 | #include 9 | #include 10 | 11 | // copied from nvme-cli :/ 12 | 13 | struct nvme_id_power_state { 14 | __le16 max_power; /* centiwatts */ 15 | __u8 rsvd2; 16 | __u8 flags; 17 | __le32 entry_lat; /* microseconds */ 18 | __le32 exit_lat; /* microseconds */ 19 | __u8 read_tput; 20 | __u8 read_lat; 21 | __u8 write_tput; 22 | __u8 write_lat; 23 | __le16 idle_power; 24 | __u8 idle_scale; 25 | __u8 rsvd19; 26 | __le16 active_power; 27 | __u8 active_work_scale; 28 | __u8 rsvd23[9]; 29 | }; 30 | 31 | struct nvme_id_ctrl { 32 | __le16 vid; 33 | __le16 ssvid; 34 | char sn[20]; 35 | char mn[40]; 36 | char fr[8]; 37 | __u8 rab; 38 | __u8 ieee[3]; 39 | __u8 cmic; 40 | __u8 mdts; 41 | __le16 cntlid; 42 | __le32 ver; 43 | __le32 rtd3r; 44 | __le32 rtd3e; 45 | __le32 oaes; 46 | __le32 ctratt; 47 | __u8 rsvd100[156]; 48 | __le16 oacs; 49 | __u8 acl; 50 | __u8 aerl; 51 | __u8 frmw; 52 | __u8 lpa; 53 | __u8 elpe; 54 | __u8 npss; 55 | __u8 avscc; 56 | __u8 apsta; 57 | __le16 wctemp; 58 | __le16 cctemp; 59 | __le16 mtfa; 60 | __le32 hmpre; 61 | __le32 hmmin; 62 | __u8 tnvmcap[16]; 63 | __u8 unvmcap[16]; 64 | __le32 rpmbs; 65 | __le16 edstt; 66 | __u8 dsto; 67 | __u8 fwug; 68 | __le16 kas; 69 | __le16 hctma; 70 | __le16 mntmt; 71 | __le16 mxtmt; 72 | __le32 sanicap; 73 | __u8 rsvd332[180]; 74 | __u8 sqes; 75 | __u8 cqes; 76 | __le16 maxcmd; 77 | __le32 nn; 78 | __le16 oncs; 79 | __le16 fuses; 80 | __u8 fna; 81 | __u8 vwc; 82 | __le16 awun; 83 | __le16 awupf; 84 | __u8 nvscc; 85 | __u8 rsvd531; 86 | __le16 acwu; 87 | __u8 rsvd534[2]; 88 | __le32 sgls; 89 | __u8 rsvd540[228]; 90 | char subnqn[256]; 91 | __u8 rsvd1024[768]; 92 | __le32 ioccsz; 93 | __le32 iorcsz; 94 | __le16 icdoff; 95 | __u8 ctrattr; 96 | __u8 msdbd; 97 | __u8 rsvd1804[244]; 98 | struct nvme_id_power_state psd[32]; 99 | __u8 vs[1024]; 100 | }; 101 | 102 | struct nvme_smart_log { 103 | __u8 critical_warning; 104 | __u8 temperature[2]; 105 | __u8 avail_spare; 106 | __u8 spare_thresh; 107 | __u8 percent_used; 108 | __u8 rsvd6[26]; 109 | __u8 data_units_read[16]; 110 | __u8 data_units_written[16]; 111 | __u8 host_reads[16]; 112 | __u8 host_writes[16]; 113 | __u8 ctrl_busy_time[16]; 114 | __u8 power_cycles[16]; 115 | __u8 power_on_hours[16]; 116 | __u8 unsafe_shutdowns[16]; 117 | __u8 media_errors[16]; 118 | __u8 num_err_log_entries[16]; 119 | __le32 warning_temp_time; 120 | __le32 critical_comp_time; 121 | __le16 temp_sensor[8]; 122 | __le32 thm_temp1_trans_count; 123 | __le32 thm_temp2_trans_count; 124 | __le32 thm_temp1_total_time; 125 | __le32 thm_temp2_total_time; 126 | __u8 rsvd232[280]; 127 | }; 128 | 129 | #define NVME_LOG_SMART 2 130 | #define NVME_ADMIN_GET_LOG_PAGE 2 131 | #define NVME_ADMIN_IDENTIFY 6 132 | 133 | static int 134 | nvme_smart_log(struct device *d, int fd){ 135 | struct nvme_admin_cmd nvmeio; 136 | struct nvme_smart_log smart; 137 | 138 | memset(&smart, 0, sizeof(smart)); 139 | memset(&nvmeio, 0, sizeof(nvmeio)); 140 | nvmeio.opcode = NVME_ADMIN_GET_LOG_PAGE; 141 | nvmeio.addr = (uintptr_t)&smart; 142 | nvmeio.data_len = sizeof(smart); 143 | // FIXME black magics stolen from nvme_get_log() 144 | nvmeio.nsid = 0xffffffffu; 145 | uint32_t numd = (nvmeio.data_len >> 2) - 1; 146 | uint16_t numdu = numd >> 16; 147 | uint16_t numdl = numd & 0xffff; 148 | nvmeio.cdw10 = NVME_LOG_SMART | (numdl << 16); 149 | nvmeio.cdw11 = numdu; 150 | if(ioctl(fd, NVME_IOCTL_ADMIN_CMD, &nvmeio)){ 151 | diag("Couldn't perform nvme_admin_get_log_page on %s:%d (%s?)\n", 152 | d->name, fd, strerror(errno)); 153 | return -1; 154 | } 155 | if(smart.critical_warning){ 156 | d->blkdev.smart = SK_SMART_OVERALL_BAD_STATUS; 157 | }else{ 158 | d->blkdev.smart = SK_SMART_OVERALL_GOOD; 159 | } 160 | // nvme smart reports temp in kelvin integer degrees, huh 161 | d->blkdev.celsius = ((smart.temperature[1] << 8) | smart.temperature[0]) - 273; 162 | return 0; 163 | } 164 | 165 | int nvme_interrogate(struct device *d, int fd){ 166 | struct nvme_admin_cmd nvmeio; 167 | struct nvme_id_ctrl ctrl; 168 | 169 | memset(&ctrl, 0, sizeof(ctrl)); 170 | memset(&nvmeio, 0, sizeof(nvmeio)); 171 | // FIXME where can we get this value from besides nvme-cli source? 172 | nvmeio.opcode = NVME_ADMIN_IDENTIFY; 173 | nvmeio.addr = (uintptr_t)&ctrl; 174 | nvmeio.data_len = sizeof(ctrl); 175 | nvmeio.cdw10 = 1; // FIXME what is this? 176 | if(ioctl(fd, NVME_IOCTL_ADMIN_CMD, &nvmeio)){ 177 | diag("Couldn't perform nvme_admin_identify on %s:%d (%s?)\n", 178 | d->name, fd, strerror(errno)); 179 | return -1; 180 | } 181 | if((d->blkdev.serial = cleanup_serial(ctrl.sn, sizeof(ctrl.sn))) == NULL){ 182 | return -1; 183 | } 184 | // NVMe devices don't appear to have WWNs...? NGUIDs are all 0s on mine? 185 | d->blkdev.wwn = strdup(d->blkdev.serial); 186 | d->blkdev.transport = DIRECT_NVME; 187 | d->blkdev.rotation = -1; // non-rotating store 188 | nvme_smart_log(d, fd); 189 | return 0; 190 | } 191 | -------------------------------------------------------------------------------- /src/nvme.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_NVME 3 | #define GROWLIGHT_NVME 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | 11 | int nvme_interrogate(struct device *, int sd); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/popen.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "popen.h" 11 | #include "growlight.h" 12 | 13 | #define REDIRECTERR " 2>&1 < /dev/null" 14 | static char * 15 | sanitize_cmd(const char *cmd){ 16 | char *tmp,*san = NULL; 17 | size_t left,len = 0; 18 | mbstate_t ps; 19 | size_t conv; 20 | 21 | memset(&ps,0,sizeof(ps)); 22 | left = strlen(cmd) + 1; 23 | do{ 24 | unsigned escape; 25 | wchar_t w; 26 | 27 | if((conv = mbrtowc(&w,cmd,left,&ps)) == (size_t)-1){ 28 | diag("Error converting multibyte: %s\n",cmd); 29 | free(san); 30 | return NULL; 31 | } 32 | if(conv == (size_t)-2){ 33 | // FIXME ended unexpectedly...are we feeding bad data? 34 | diag("Multibyte ended unexpectedly: %s\n",cmd); 35 | break; 36 | } 37 | if(conv == 0){ // done! 38 | break; 39 | } 40 | left -= conv; 41 | if(w == L'(' || w == L')'){ 42 | escape = 1; 43 | }else if(w == '$'){ 44 | escape = 1; 45 | }else{ 46 | escape = 0; 47 | } 48 | if((tmp = realloc(san,sizeof(*san) * (len + conv + escape))) == NULL){ 49 | free(san); 50 | return NULL; 51 | } 52 | san = tmp; 53 | if(escape){ 54 | san[len] = '\\'; 55 | ++len; 56 | } 57 | memcpy(san + len,cmd,conv); 58 | len += conv; 59 | cmd += conv; 60 | }while(conv); 61 | if((tmp = realloc(san,sizeof(*san) * (len + 1 + strlen(REDIRECTERR)))) == NULL){ 62 | free(san); 63 | return NULL; 64 | } 65 | san = tmp; 66 | strcpy(san + len,REDIRECTERR); 67 | return san; 68 | } 69 | 70 | int popen_drain(const char *cmd){ 71 | char buf[128],*safecmd; 72 | FILE *fd; 73 | 74 | if((safecmd = sanitize_cmd(cmd)) == NULL){ 75 | return -1; 76 | } 77 | diag("Running \"%s\"...\n",safecmd); 78 | if((fd = popen(safecmd,"re")) == NULL){ 79 | diag("Couldn't run %s (%s?)\n",safecmd,strerror(errno)); 80 | free(safecmd); 81 | return -1; 82 | } 83 | while(fgets(buf,sizeof(buf),fd)){ 84 | diag("%s",buf); 85 | } 86 | if(!feof(fd)){ 87 | diag("Error reading from '%s' (%s?)\n",safecmd,strerror(errno)); 88 | pclose(fd); 89 | return -1; 90 | } 91 | if(pclose(fd)){ 92 | diag("Error running '%s'\n",safecmd); 93 | return -1; 94 | } 95 | return 0; 96 | } 97 | 98 | int vpopen_drain(const char *cmd,wchar_t * const *args){ 99 | char buf[BUFSIZ]; 100 | int r; 101 | 102 | if((r = snprintf(buf,sizeof(buf),"%s ",cmd)) >= (int)sizeof(buf)){ 103 | return -1; 104 | } 105 | while(*args){ 106 | int rr; 107 | 108 | if((rr = snprintf(buf + r,sizeof(buf) - r,"%ls ",*args)) >= (int)(sizeof(buf) - r)){ 109 | return -1; 110 | } 111 | r += rr; 112 | ++args; 113 | } 114 | return popen_drain(buf); 115 | } 116 | 117 | int vspopen_drain(const char *fmt,...){ 118 | char buf[BUFSIZ]; 119 | va_list va; 120 | 121 | va_start(va,fmt); 122 | if(vsnprintf(buf,sizeof(buf),fmt,va) >= (int)sizeof(buf)){ 123 | va_end(va); 124 | diag("Bad command: %s ...\n",fmt); 125 | return -1; 126 | } 127 | va_end(va); 128 | return popen_drain(buf); 129 | } 130 | -------------------------------------------------------------------------------- /src/popen.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_POPEN 3 | #define GROWLIGHT_POPEN 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | 11 | int popen_drain(const char *); 12 | int vpopen_drain(const char *,wchar_t * const *); 13 | int vspopen_drain(const char *,...) __attribute__ ((format (printf,1,2))); 14 | 15 | #ifdef __cplusplus 16 | } 17 | #endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/ptable.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_PTABLE 3 | #define GROWLIGHT_PTABLE 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | 11 | struct device; 12 | 13 | #define MBR_OFFSET 440u 14 | 15 | // Create the given type of partition table on this device 16 | int make_partition_table(struct device *,const char *); 17 | 18 | // Wipe the partition table (make it unrecognizable, preferably by overwriting 19 | // it with zeroes). If a ptype is specified, it is assumed that this partition 20 | // table type is being used, and we will zero out according to the specified 21 | // type, even if it doesn't match the detected type (very dangerous!). If no 22 | // type is specified, the detected type, if it exists, is used. 23 | int wipe_ptable(struct device *,const char *); 24 | 25 | int add_partition(struct device *,const wchar_t *,uintmax_t,uintmax_t,unsigned long long); 26 | int wipe_partition(const struct device *); 27 | int name_partition(struct device *,const wchar_t *); 28 | int uuid_partition(struct device *,const void *); 29 | int check_partition(struct device *); 30 | int partition_set_flags(struct device *,uint64_t); 31 | int partition_set_flag(struct device *,uint64_t,unsigned); 32 | int partition_set_code(struct device *,unsigned long long); 33 | int partitions_named_p(const struct device *); 34 | 35 | uintmax_t lookup_first_usable_sector(const struct device *); 36 | uintmax_t lookup_last_usable_sector(const struct device *); 37 | 38 | // Interface to kernel's BLKPG ioctl 39 | int blkpg_add_partition(int,long long,long long,int,const char *); 40 | int blkpg_del_partition(int,long long,long long,int,const char *); 41 | 42 | #ifdef __cplusplus 43 | } 44 | #endif 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/ptypes.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include "ptypes.h" 5 | #include "growlight.h" 6 | 7 | // Ensure the longer matches (GPT) precede the shorter ones (MSDOS), or common 8 | // prefixes will cause bad matches. 9 | const ptype ptypes[] = { 10 | { 11 | .code = 0x0700, 12 | .name = "Microsoft basic data", 13 | .gpt_guid = "\xA2\xA0\xD0\xEB\xE5\xB9\x33\x44\x87\xC0\x68\xB6\xB7\x26\x99\xC7", 14 | .mbr_code = 0, 15 | }, { 16 | .code = 0x0c01, 17 | .name = "Microsoft reserved", 18 | .gpt_guid = "\x16\xE3\xC9\xE3\x5C\x0B\xB8\x4D\x81\x7D\xF9\x2D\xF0\x02\x15\xAE", 19 | .mbr_code = 0, 20 | }, { 21 | .code = 0x2700, 22 | .name = "Windows Recovery Environment", 23 | .gpt_guid = "\xA4\xBB\x94\xDE\xD1\x06\x40\x4D\xA1\x6A\xBF\xD5\x01\x79\xD6\xAC", 24 | .mbr_code = 0, 25 | }, { 26 | .code = 0x4200, 27 | .name = "Windows Logical Disk Manager data", 28 | .gpt_guid = "\xA0\x60\x9B\xAF\x31\x14\x62\x4F\xBC\x68\x33\x11\x71\x4A\x69\xAD", 29 | .mbr_code = 0, 30 | }, { 31 | .code = 0x4201, 32 | .name = "Windows Logical Disk Manager metadata", 33 | .gpt_guid = "\xAA\xC8\x08\x58\x8F\x7E\xE0\x42\x85\xD2\xE1\xE9\x04\x34\xCF\xB3", 34 | .mbr_code = 0, 35 | }, { 36 | .code = 0x7501, 37 | .name = "IBM General Parallel File System", 38 | .gpt_guid = "\x90\xFC\xAF\x37\x7D\xEF\x96\x4E\x91\xC3\x2D\x7A\xE0\x55\xB1\x74", 39 | .mbr_code = 0, 40 | }, { 41 | .code = 0x7f00, 42 | .name = "ChromeOS kernel", 43 | .gpt_guid = "\x5D\x2A\x3A\xFE\x32\x4F\xA7\x41\xB7\x25\xAC\xCC\x32\x85\xA3\x09", 44 | .mbr_code = 0, 45 | }, { 46 | .code = 0x7f01, 47 | .name = "ChromeOS root", 48 | .gpt_guid = "\x02\xE2\xB8\x3C\x7E\x3B\xDD\x47\x8A\x3C\x7F\xF2\xA1\x3C\xFC\xEC", 49 | .mbr_code = 0, 50 | }, { 51 | .code = 0x7f02, 52 | .name = "ChromeOS reserved", 53 | .gpt_guid = "\x3D\x75\x0A\x2E\x48\x9E\xB0\x43\x83\x37\xB1\x51\x92\xCB\x1B\x5E", 54 | .mbr_code = 0, 55 | }, { 56 | .code = 0x8200, 57 | .name = "Linux swap", 58 | .gpt_guid = "\x6D\xFD\x57\x06\xAB\xA4\xC4\x43\x84\xE5\x09\x33\xC8\x4B\x4F\x4F", 59 | .mbr_code = 0x82, 60 | }, { 61 | .code = PARTROLE_PRIMARY, 62 | .name = "Linux filesystem", 63 | .gpt_guid = "\xAF\x3D\xC6\x0F\x83\x84\x72\x47\x8E\x79\x3D\x69\xD8\x47\x7D\xE4", 64 | .mbr_code = 0x83, 65 | }, { 66 | .code = 0x8301, 67 | .name = "Linux reserved", 68 | .gpt_guid = "\x39\x33\xA6\x8D\x07\x00\xC0\x60\xC4\x36\x08\x3A\xC8\x23\x09\x08", 69 | .mbr_code = 0, 70 | }, { 71 | .code = 0x8400, 72 | .name = "Intel Rapid Start Technology", 73 | .gpt_guid = "\xD3\xBF\xE2\xDE\x3D\xAF\x11\xDF\xBA\x40\xE3\xA5\x56\xD8\x95\x93", 74 | .mbr_code = 0x84, 75 | }, { 76 | .code = 0x8e00, 77 | .name = "Linux Logical Volume Manager", 78 | .gpt_guid = "\x79\xD3\xD6\xE6\x07\xF5\xC2\x44\xA2\x3C\x23\x8F\x2A\x3D\xF9\x28", 79 | .mbr_code = 0x8e, 80 | }, /*{ 81 | .code = 0xa500, 82 | .name = "FreeBSD disklabel", 83 | .gpt_guid = label 84 | 85 | .mbr_code = 0, 86 | },*/ { 87 | .code = 0xa501, 88 | .name = "FreeBSD boot", 89 | .gpt_guid = "\x9D\x6B\xBD\x83\x41\x7F\xDC\x11\xBE\x0B\x00\x15\x60\xB8\x4F\x0F", 90 | .mbr_code = 0xa5, 91 | }, { 92 | .code = 0xa502, 93 | .name = "FreeBSD swap", 94 | .gpt_guid = "\xB5\x7C\x6E\x51\xCF\x6E\xD6\x11\x8F\xF8\x00\x02\x2D\x09\x71\x2B", 95 | .mbr_code = 0xa5, 96 | }, { 97 | .code = 0xa503, 98 | .name = "FreeBSD UFS", 99 | .gpt_guid = "\xB6\x7C\x6E\x51\xCF\x6E\xD6\x11\x8F\xF8\x00\x02\x2D\x09\x71\x2B", 100 | .mbr_code = 0xa5, 101 | }, { 102 | .code = 0xa504, 103 | .name = "FreeBSD/Linux ZFS", 104 | .gpt_guid = "\xBA\x7C\x6E\x51\xCF\x6E\xD6\x11\x8F\xF8\x00\x02\x2D\x09\x71\x2B", 105 | .mbr_code = 0xa5, 106 | .aggregable = 1, 107 | }, { 108 | .code = 0xa505, 109 | .name = "FreeBSD Vinum/RAID", 110 | .gpt_guid = "\xB8\x7C\x6E\x51\xCF\x6E\xD6\x11\x8F\xF8\x00\x02\x2D\x09\x71\x2B", 111 | .mbr_code = 0xa5, 112 | .aggregable = 1, 113 | }, { 114 | .code = PARTROLE_ESP, 115 | .name = "EFI System Partition (ESP)", 116 | .gpt_guid = "\x28\x73\x2A\xC1\x1F\xF8\xD2\x11\xBA\x4B\x00\xA0\xC9\x3E\xC9\x3B", 117 | .mbr_code = 0, 118 | }, { 119 | .code = 0xef01, 120 | .name = "MBR partition scheme", 121 | .gpt_guid = "\x41\xEE\x4D\x02\xE7\x33\xD3\x11\x9D\x69\x00\x08\xC7\x81\xF3\x9F", 122 | .mbr_code = 0, 123 | }, { 124 | .code = PARTROLE_BIOSBOOT, 125 | .name = "BIOS boot partition", 126 | .gpt_guid = "\x48\x61\x68\x21\x49\x64\x6F\x6E\x74\x4E\x65\x65\x64\x45\x46\x49", 127 | .mbr_code = 0, 128 | }, { 129 | .code = 0xfd00, 130 | .name = "Linux MDRAID", 131 | .gpt_guid = "\x0F\x88\x9D\xA1\xFC\x05\x3B\x4D\xA0\x06\x74\x3F\x0F\x84\x91\x1E", 132 | .mbr_code = 0xfd, 133 | .aggregable = 1, 134 | }, { 135 | .code = 0xa580, 136 | .name = "Midnight BSD data", 137 | .gpt_guid = "\x5a\xe4\xd5\x85\x7c\x23\xe1\x11\xb4\xb3\xe8\x9a\x8f\x7f\xc3\xa7", 138 | .mbr_code = 0, 139 | }, { 140 | .code = 0xa581, 141 | .name = "Midnight BSD boot", 142 | .gpt_guid = "\x5e\xe4\xd5\x85\x7c\x23\xe1\x11\xb4\xb3\xe8\x9a\x8f\x7f\xc3\xa7", 143 | .mbr_code = 0, 144 | }, { 145 | .code = 0xa582, 146 | .name = "Midnight BSD swap", 147 | .gpt_guid = "\x5b\xe4\xd5\x85\x7c\x23\xe1\x11\xb4\xb3\xe8\x9a\x8f\x7f\xc3\xa7", 148 | .mbr_code = 0, 149 | }, { 150 | .code = 0xa583, 151 | .name = "Midnight BSD UFS", 152 | .gpt_guid = "\x8b\xef\x94\x03\x7e\x23\xe1\x11\xb4\xb3\xe8\x9a\x8f\x7f\xc3\xa7", 153 | .mbr_code = 0, 154 | }, { 155 | .code = 0xa584, 156 | .name = "Midnight BSD ZFS", 157 | .gpt_guid = "\x5d\xe4\xd5\x85\x7c\x23\xe1\x11\xb4\xb3\xe8\x9a\x8f\x7f\xc3\xa7", 158 | .mbr_code = 0, 159 | .aggregable = 1, 160 | }, { 161 | .code = 0xa585, 162 | .name = "Midnight BSD Vinum/RAID", 163 | .gpt_guid = "\x5c\xe4\xd5\x85\x7c\x23\xe1\x11\xb4\xb3\xe8\x9a\x8f\x7f\xc3\xa7", 164 | .mbr_code = 0, 165 | .aggregable = 1, 166 | }, { 167 | .code = 0xa800, 168 | .name = "Apple UFS", 169 | .gpt_guid = "\x00\x53\x46\x55\x00\x00\xAA\x11\xAA\x11\x00\x30\x65\x43\xEC\xAC", 170 | .mbr_code = 0xa8, 171 | }, { 172 | .code = 0xa901, 173 | .name = "NetBSD swap", 174 | .gpt_guid = "\x32\x8D\xF4\x49\x0E\xB1\xDC\x11\xB9\x9B\x00\x19\xD1\x87\x96\x48", 175 | .mbr_code = 0xa9, 176 | }, { 177 | .code = 0xa902, 178 | .name = "NetBSD FFS", 179 | .gpt_guid = "\x5A\x8D\xF4\x49\x0E\xB1\xDC\x11\xB9\x9B\x00\x19\xD1\x87\x96\x48", 180 | .mbr_code = 0xa9, 181 | }, { 182 | .code = 0xa903, 183 | .name = "NetBSD LFS", 184 | .gpt_guid = "\x82\x8D\xF4\x49\x0E\xB1\xDC\x11\xB9\x9B\x00\x19\xD1\x87\x96\x48", 185 | .mbr_code = 0xa9, 186 | }, { 187 | .code = 0xa904, 188 | .name = "NetBSD concatenated", 189 | .gpt_guid = "\xC4\x19\xB5\x2D\x0F\xB1\xDC\x11\xB9\x9B\x00\x19\xD1\x87\x96\x48", 190 | .mbr_code = 0xa9, 191 | }, { 192 | .code = 0xa905, 193 | .name = "NetBSD encrypted filesystem", 194 | .gpt_guid = "\xEC\x19\xB5\x2D\x0F\xB1\xDC\x11\xB9\x9B\x00\x19\xD1\x87\x96\x48", 195 | .mbr_code = 0xa9, 196 | }, { 197 | .code = 0xa906, 198 | .name = "NetBSD RAID", 199 | .gpt_guid = "\xAA\x8D\xF4\x49\x0E\xB1\xDC\x11\xB9\x9B\x00\x19\xD1\x87\x96\x48", 200 | .mbr_code = 0xa9, 201 | .aggregable = 1, 202 | }, { 203 | .code = 0xab00, 204 | .name = "Apple boot", 205 | .gpt_guid = "\x74\x6F\x6F\x42\x00\x00\xAA\x11\xAA\x11\x00\x30\x65\x43\xEC\xAC", 206 | .mbr_code = 0xab, 207 | }, { 208 | .code = 0xaf00, 209 | .name = "Apple HFS/HFS+", 210 | .gpt_guid = "\x00\x53\x46\x48\x00\x00\xAA\x11\xAA\x11\x00\x30\x65\x43\xEC\xAC", 211 | .mbr_code = 0, 212 | }, { 213 | .code = 0xaf01, 214 | .name = "Apple RAID", 215 | .gpt_guid = "\x44\x49\x41\x52\x00\x00\xAA\x11\xAA\x11\x00\x30\x65\x43\xEC\xAC", 216 | .mbr_code = 0, 217 | .aggregable = 1, 218 | }, { 219 | .code = 0xaf02, 220 | .name = "Apple RAID offline", 221 | .gpt_guid = "\x44\x49\x41\x52\x4F\x5F\xAA\x11\xAA\x11\x00\x30\x65\x43\xEC\xAC", 222 | .mbr_code = 0, 223 | .aggregable = 1, 224 | }, { 225 | .code = 0xaf03, 226 | .name = "Apple label", 227 | .gpt_guid = "\x65\x62\x61\x4C\x00\x6C\xAA\x11\xAA\x11\x00\x30\x65\x43\xEC\xAC", 228 | .mbr_code = 0, 229 | }, { 230 | .code = 0xaf04, 231 | .name = "AppleTV recovery", 232 | .gpt_guid = "\x6F\x63\x65\x52\x65\x76\xAA\x11\xAA\x11\x00\x30\x65\x43\xEC\xAC", 233 | .mbr_code = 0, 234 | }, { 235 | .code = 0xaf05, 236 | .name = "Apple Core Storage", 237 | .gpt_guid = "\x72\x6F\x74\x53\x67\x61\xAA\x11\xAA\x11\x00\x30\x65\x43\xEC\xAC", 238 | .mbr_code = 0, 239 | }, { 240 | .code = 0xbe00, 241 | .name = "Solaris boot", 242 | .gpt_guid = "\x45\xCB\x82\x6A\xD2\x1D\xB2\x11\x99\xA6\x08\x00\x20\x73\x66\x31", 243 | .mbr_code = 0xbe, 244 | }, { 245 | .code = 0xbf00, 246 | .name = "Solaris root", 247 | .gpt_guid = "\x4D\xCF\x85\x6A\xD2\x1D\xB2\x11\x99\xA6\x08\x00\x20\x73\x66\x31", 248 | .mbr_code = 0xbf, 249 | }, { 250 | .code = 0xbf01, 251 | // .name = "Solaris /usr, Mac OS X ZFS", 252 | .name = "ZFS", 253 | .gpt_guid = "\xC3\x8C\x89\x6A\xD2\x1D\xB2\x11\x99\xA6\x08\x00\x20\x73\x66\x31", 254 | .mbr_code = 0xbf, 255 | .aggregable = 1, 256 | }, { 257 | .code = 0xbf02, 258 | .name = "Solaris swap", 259 | .gpt_guid = "\x6F\xC4\x87\x6A\xD2\x1D\xB2\x11\x99\xA6\x08\x00\x20\x73\x66\x31", 260 | .mbr_code = 0xbf, 261 | }, { 262 | .code = 0xbf03, 263 | .name = "Solaris backup", 264 | .gpt_guid = "\x2B\x64\x8B\x6A\xD2\x1D\xB2\x11\x99\xA6\x08\x00\x20\x73\x66\x31", 265 | .mbr_code = 0xbf, 266 | }, { 267 | .code = 0xbf04, 268 | .name = "Solaris /var", 269 | .gpt_guid = "\xE9\xF2\x8E\x6A\xD2\x1D\xB2\x11\x99\xA6\x08\x00\x20\x73\x66\x31", 270 | .mbr_code = 0xbf, 271 | }, { 272 | .code = 0xbf05, 273 | .name = "Solaris /home", 274 | .gpt_guid = "\x39\xBA\x90\x6A\xD2\x1D\xB2\x11\x99\xA6\x08\x00\x20\x73\x66\x31", 275 | .mbr_code = 0xbf, 276 | }, { 277 | .code = 0xbf07, 278 | .name = "Solaris reserved", 279 | .gpt_guid = "\x3b\x5A\x94\x6A\xD2\x1D\xB2\x11\x99\xA6\x08\x00\x20\x73\x66\x31", 280 | .mbr_code = 0xbf, 281 | }, { 282 | .code = 0xc001, 283 | .name = "HP/UX data", 284 | .gpt_guid = "\x1E\x4C\x89\x75\xEB\x3A\xD3\x11\xB7\xC1\x7B\x03\xA0\x00\x00\x00", 285 | .mbr_code = 0, 286 | }, { 287 | .code = 0xc002, 288 | .name = "HP/UX service partition", 289 | .gpt_guid = "\x28\xE7\xA1\xE2\xE3\x32\xD6\x11\xA6\x82\x7B\x03\xA0\x00\x00\x00", 290 | .mbr_code = 0, 291 | }, { 292 | .code = 0x0005, 293 | .name = "DOS extended", 294 | .gpt_guid = {0}, 295 | .mbr_code = 0x5, 296 | }, { 297 | .code = 0x0006, 298 | .name = "FAT16", 299 | .gpt_guid = {0}, 300 | .mbr_code = 0x06, 301 | }, { 302 | .code = 0x0008, 303 | .name = "AIX", 304 | .gpt_guid = {0}, 305 | .mbr_code = 0x08, 306 | }, { 307 | .code = 0x0009, 308 | .name = "AIX Bootable", 309 | .gpt_guid = {0}, 310 | .mbr_code = 0x09, 311 | }, { 312 | .code = 0x000b, 313 | .name = "FAT32", 314 | .gpt_guid = {0}, 315 | .mbr_code = 0x0b, 316 | }, { 317 | .code = 0x000c, 318 | .name = "FAT32 LBA", 319 | .gpt_guid = {0}, 320 | .mbr_code = 0x0c, 321 | }, { 322 | .code = 0x000e, 323 | .name = "FAT16 LBA", 324 | .gpt_guid = {0}, 325 | .mbr_code = 0x0e, 326 | }, { 327 | .code = 0x0085, 328 | .name = "Linux extended", 329 | .gpt_guid = {0}, 330 | .mbr_code = 0x85, 331 | }, { 332 | .code = 0x00a6, 333 | .name = "OpenBSD", 334 | .gpt_guid = {0}, 335 | .mbr_code = 0xa6, 336 | }, { 337 | .code = 0x00ee, 338 | .name = "MBR Protective", 339 | .gpt_guid = {0}, 340 | .mbr_code = 0xee, 341 | }, { 342 | .code = 0x00ef, 343 | .name = "EFI FAT", 344 | .gpt_guid = {0}, 345 | .mbr_code = 0xef, 346 | }, { 347 | .code = 0, 348 | .name = NULL, 349 | .gpt_guid = { 0 }, 350 | .mbr_code = 0, 351 | }, 352 | }; 353 | 354 | /* 355 | printf("MBR types:\n"); 356 | printf(" 0 Empty 1e Hidd FAT16 LBA 80 Minix <1.4a \n" 357 | " 1 FAT12 24 NEC DOS 81 Minix >1.4b c1 DRDOS/2 FAT12 \n" 358 | " 2 XENIX root 39 Plan 9 c4 DRDOS/2 smFAT16 \n" 359 | " 3 XENIX usr 3c PMagic recovery c6 DRDOS/2 FAT16 \n" 360 | " 4 Small FAT16 40 Venix 80286 84 OS/2 hidden C: c7 Syrinx \n" 361 | " 41 PPC PReP Boot da Non-FS data \n" 362 | " 42 SFS 86 NTFS volume set db CP/M / CTOS \n" 363 | " 7 HPFS/NTFS 4d QNX4.x 87 NTFS volume set de Dell Utility \n" 364 | " 4e QNX4.x 2nd part 88 Linux plaintext df BootIt \n" 365 | " 4f QNX4.x 3rd part e1 DOS access \n" 366 | " a OS/2 boot mgr 50 OnTrack DM 93 Amoeba e3 DOS R/O \n" 367 | " 51 OnTrackDM6 Aux1 94 Amoeba BBT e4 SpeedStor \n" 368 | " 52 CP/M 9f BSD/OS eb BeOS fs \n" 369 | " 53 OnTrackDM6 Aux3 a0 Thinkpad hib \n" 370 | " f Extended LBA 54 OnTrack DM6 \n" 371 | " 10 OPUS 55 EZ Drive f0 Lnx/PA-RISC bt \n" 372 | " 11 Hidden FAT12 56 Golden Bow a7 NeXTSTEP f1 SpeedStor \n" 373 | " 12 Compaq diag 5c Priam Edisk f2 DOS secondary \n" 374 | " 14 Hidd Sm FAT16 61 SpeedStor f4 SpeedStor \n" 375 | " 16 Hidd FAT16 63 GNU HURD/SysV " 376 | " 17 Hidd HPFS/NTFS 64 Netware 286 b7 BSDI fs fe LANstep \n" 377 | " 18 AST SmartSleep 65 Netware 386 b8 BSDI swap ff XENIX BBT \n" 378 | " 1b Hidd FAT32 70 DiskSec MltBoot bb Boot Wizard Hid \n" 379 | " 1c Hidd FAT32 LBA 75 PC/IX \n"); 380 | return 0;*/ 381 | 382 | // Pass in the common code, get the scheme-specific identifier filled in. 383 | // Returns 0 for a valid code, or -1 if there's no ident for the scheme. 384 | int get_gpt_guid(unsigned code, void *guid){ 385 | const ptype *pt; 386 | 387 | for(pt = ptypes ; pt->name ; ++pt){ 388 | if(pt->code == code){ 389 | static const uint8_t zguid[GUIDSIZE] = {0}; 390 | 391 | if(memcmp(pt->gpt_guid, zguid, sizeof(zguid)) == 0){ 392 | return -1; 393 | } 394 | memcpy(guid, pt->gpt_guid, GUIDSIZE); 395 | return 0; 396 | } 397 | } 398 | return -1; 399 | } 400 | 401 | int get_mbr_code(unsigned code,unsigned *mbr){ 402 | const ptype *pt; 403 | 404 | for(pt = ptypes ; pt->name ; ++pt){ 405 | if(pt->code == code){ 406 | if(pt->mbr_code == 0){ 407 | return -1; 408 | } 409 | *mbr = pt->mbr_code; 410 | return 0; 411 | } 412 | } 413 | return -1; 414 | } 415 | 416 | int ptype_supported(const char *pttype,const ptype *pt){ 417 | if(strcmp(pttype,"gpt") == 0){ 418 | static const uint8_t zguid[GUIDSIZE] = {0}; 419 | 420 | if(memcmp(pt->gpt_guid,zguid,sizeof(zguid)) == 0){ 421 | return 0; 422 | } 423 | return 1; 424 | }else if(strcmp(pttype,"mbr") == 0 || strcmp(pttype,"dos") == 0){ 425 | if(pt->mbr_code == 0){ 426 | return 0; 427 | } 428 | return 1; 429 | }else if(strcmp(pttype,"mdp") == 0){ 430 | return 0; 431 | } 432 | diag("No support for pttype %s\n",pttype); 433 | return 0; 434 | } 435 | 436 | // Get the numeric partition type from a libblkid PART_TYPE value. 437 | unsigned get_str_code(const char *str){ 438 | unsigned long ul; 439 | const ptype *pt; 440 | char *e; 441 | 442 | // libblkid (currently) uses "0x%2x" as a format specifier. by using 443 | // strtoul, we ought be fairly future-proof. 444 | if((ul = strtoul(str, &e, 16)) > 0xff || e - str > 2){ 445 | ul = 0; 446 | } 447 | for(pt = ptypes ; pt->name ; ++pt){ 448 | char tstr[GUIDSTRLEN + 1]; 449 | 450 | guidstr_be(pt->gpt_guid,tstr); 451 | if(strcmp(tstr,str) == 0){ 452 | return pt->code; 453 | } 454 | if(ul && ul == pt->mbr_code){ 455 | return pt->code; 456 | } 457 | } 458 | return 0; 459 | } 460 | 461 | unsigned get_code_specific(const char *pttype,unsigned code){ 462 | const ptype *pt; 463 | 464 | for(pt = ptypes ; pt->name ; ++pt){ 465 | if(pt->code != code){ 466 | continue; 467 | } 468 | if(strcmp(pttype,"gpt") == 0){ 469 | return code; 470 | }else if(strcmp(pttype,"mbr") == 0 || strcmp(pttype,"dos") == 0){ 471 | if(pt->mbr_code == 0){ 472 | return code; 473 | } 474 | return pt->mbr_code; 475 | }else if(strcmp(pttype,"mdp") == 0){ 476 | return code; 477 | }else{ 478 | return code; 479 | } 480 | return code; 481 | } 482 | return code; 483 | } 484 | -------------------------------------------------------------------------------- /src/ptypes.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_PTYPES 3 | #define GROWLIGHT_PTYPES 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | #include "gpt.h" 11 | 12 | #define PARTROLE_ESP 0xef00 13 | #define PARTROLE_BIOSBOOT 0xef02 14 | #define PARTROLE_PRIMARY 0x8300 15 | 16 | typedef struct ptype { 17 | uint16_t code; // [fg]disk/parted code (2 bytes) 18 | const char *name; // Human-readable name 19 | uint8_t gpt_guid[GUIDSIZE]; // GPT Type GUID (16 bytes) or 0s 20 | uint8_t mbr_code; // MBR code (1 byte) or 0 21 | unsigned aggregable; // Can it go into an aggregate? 22 | } ptype; 23 | 24 | extern const ptype ptypes[]; 25 | 26 | // Pass in the common code, get the scheme-specific identifier filled in. 27 | // Returns 0 for a valid code, or -1 if there's no ident for the scheme. 28 | int get_gpt_guid(unsigned,void *); 29 | int get_mbr_code(unsigned,unsigned *); 30 | 31 | // Pass in a libblkid-style string representation, and get the common code 32 | unsigned get_str_code(const char *); 33 | 34 | unsigned get_code_specific(const char *,unsigned); 35 | 36 | static inline int 37 | ptype_default_p(unsigned code){ 38 | return code == PARTROLE_PRIMARY; 39 | } 40 | 41 | int ptype_supported(const char *,const ptype *); 42 | 43 | #ifdef __cplusplus 44 | } 45 | #endif 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/secure.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "popen.h" 8 | #include "secure.h" 9 | #include "growlight.h" 10 | 11 | int ata_secure_erase(device *d){ 12 | if(d->layout != LAYOUT_NONE){ 13 | diag("Can only run ATA Erase on ATA-connected blockdevs\n"); 14 | return -1; 15 | } 16 | if(vspopen_drain("hdparm --user-master u --security-set-pass erasepw /dev/%s", d->name)){ 17 | diag("Couldn't set ATA user password\n"); 18 | return -1; 19 | } 20 | if(vspopen_drain("hdparm --user-master u --security-erase erasepw /dev/%s", d->name)){ 21 | diag("Couldn't perform ATA Secure Erase\n"); 22 | return -1; 23 | } 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/secure.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_SECURE 3 | #define GROWLIGHT_SECURE 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | 11 | int ata_secure_erase(struct device *); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/sg.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black, except where otherwise noted below 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "sg.h" 17 | #include "sysfs.h" 18 | #include "growlight.h" 19 | 20 | // Consult T10/04-262r3 "ATA Comand Pass-Through" revision 3 2004-08-31 21 | // The following defines and enums are taken from sgio.h from hdparm 9.58. 22 | // That file is copyright Mark Lord (mlord@pobox.com), and licensed as follows: 23 | // 24 | // You may freely use, modify, and redistribute the hdparm program, 25 | // as either binary or source, or both. 26 | // 27 | // The only condition is that my name and copyright notice 28 | // remain in the source code as-is. 29 | // 30 | // Mark Lord (mlord@pobox.com) 31 | static const int SG_ATA_16 = 0x85; // 16-byte ATA pass-though command 32 | #define SG_ATA_16_LEN 16 33 | static const int SG_ATA_PROTO_PIO_IN = 4; 34 | #define SG_CHECK_CONDITION 0x02 35 | #define SG_DRIVER_SENSE 0x08 36 | #define START_SERIAL 10 // ASCII serial number 37 | #define LENGTH_SERIAL 20 38 | #define CMDS_SUPP_0 82 // command/feature set(s) supported 39 | #define FEATURE_WRITE_CACHE 16 // use with CMDS_SUPP_1 40 | #define CMDS_SUPP_1 83 41 | #define CMDS_SUPP_2 84 42 | #define CMDS_SUPP_3 119 43 | #define FEATURE_READWRITEVERIFY 2 // use with CMDS_SUPP_3 44 | #define CMDS_EN_0 85 // command/feature set(s) enabled 45 | #define CMDS_EN_1 86 46 | #define CMDS_EN_2 87 47 | #define CMDS_EN_3 120 48 | #define TRANSPORT_MAJOR 222 49 | #define TRANSPORT_MINOR 223 50 | #define NMRR 217 51 | #define WWN_SUP 0x100 52 | 53 | enum { 54 | SG_CDB2_TLEN_NSECT = 2 << 0, 55 | SG_CDB2_TLEN_SECTORS = 1 << 2, 56 | SG_CDB2_TDIR_FROM_DEV = 1 << 3, 57 | }; 58 | 59 | enum { 60 | ATA_USING_LBA = (1 << 6), 61 | }; 62 | 63 | enum { 64 | ATA_OP_PIDENTIFY = 0xa1, 65 | ATA_OP_IDENTIFY = 0xec, 66 | }; 67 | 68 | struct scsi_sg_io_hdr { 69 | int interface_id; 70 | int dxfer_direction; 71 | unsigned char cmd_len; 72 | unsigned char mx_sb_len; 73 | unsigned short iovec_count; 74 | unsigned int dxfer_len; 75 | void * dxferp; 76 | unsigned char * cmdp; 77 | void * sbp; 78 | unsigned int timeout; 79 | unsigned int flags; 80 | int pack_id; 81 | void * usr_ptr; 82 | unsigned char status; 83 | unsigned char masked_status; 84 | unsigned char msg_status; 85 | unsigned char sb_len_wr; 86 | unsigned short host_status; 87 | unsigned short driver_status; 88 | int resid; 89 | unsigned int duration; 90 | unsigned int info; 91 | }; 92 | // Material taken from hdparm ends here 93 | 94 | int sg_interrogate(device *d, int fd){ 95 | #define IDSECTORS 1 96 | unsigned char cdb[SG_ATA_16_LEN]; 97 | uint16_t buf[512 / 2], maj, min; // FIXME 98 | struct scsi_sg_io_hdr io; 99 | char sb[32]; 100 | unsigned n; 101 | 102 | assert(d->layout == LAYOUT_NONE); 103 | memset(buf, 0, sizeof(buf)); 104 | memset(cdb, 0, sizeof(cdb)); 105 | cdb[0] = SG_ATA_16; 106 | cdb[1] = SG_ATA_PROTO_PIO_IN << 1u; 107 | cdb[2] = SG_CDB2_TLEN_NSECT | SG_CDB2_TLEN_SECTORS | SG_CDB2_TDIR_FROM_DEV; 108 | cdb[6] = IDSECTORS; 109 | cdb[13] = ATA_USING_LBA; 110 | cdb[14] = ATA_OP_IDENTIFY; 111 | // data size: 512 112 | memset(&io, 0, sizeof(io)); 113 | io.interface_id = 'S'; 114 | io.mx_sb_len = sizeof(sb); 115 | io.dxfer_direction = SG_DXFER_FROM_DEV; 116 | io.dxfer_len = sizeof(buf); 117 | io.dxferp = buf; 118 | io.cmdp = cdb; 119 | io.sbp = sb; 120 | io.cmd_len = sizeof(cdb); 121 | if(ioctl(fd, SG_IO, &io)){ 122 | diag("Couldn't perform SG_IO ioctl on %s:%d (%s?)\n", d->name, fd, strerror(errno)); 123 | return -1; 124 | } 125 | if(io.driver_status && io.driver_status != SG_DRIVER_SENSE){ 126 | verbf("Bad driver status 0x%x on %s\n", io.driver_status, d->name); 127 | cdb[14] = ATA_OP_PIDENTIFY; 128 | if(ioctl(fd, SG_IO, &io)){ 129 | diag("Couldn't perform PIDENTIFY ioctl on %s:%d (%s?)\n", d->name, fd, strerror(errno)); 130 | return -1; 131 | } 132 | if(io.driver_status && io.driver_status != SG_DRIVER_SENSE){ 133 | verbf("Bad PIDENTIFY status 0x%x on %s\n", io.driver_status, d->name); 134 | return -1; 135 | } 136 | } 137 | if(io.status && io.status != SG_CHECK_CONDITION){ 138 | verbf("Bad check condition 0x%x on %s\n", io.status, d->name); 139 | return 0; // FIXME 140 | } 141 | if(io.host_status){ 142 | verbf("Bad host status 0x%x on %s\n", io.host_status, d->name); 143 | return 0; // FIXME 144 | } 145 | maj = buf[TRANSPORT_MAJOR] >> 12u; 146 | min = buf[TRANSPORT_MAJOR] & 0xfffu; 147 | switch(maj){ 148 | case 0: 149 | d->blkdev.transport = PARALLEL_ATA; 150 | break; 151 | case 1: 152 | if(min & (1u << 5u)){ 153 | d->blkdev.transport = SERIAL_ATAIII; 154 | }else if(min & (1u << 2u)){ 155 | d->blkdev.transport = SERIAL_ATAII; 156 | }else if(min & (1u << 1u)){ 157 | d->blkdev.transport = SERIAL_ATAI; 158 | }else if(min & 1u){ 159 | d->blkdev.transport = SERIAL_ATA8; 160 | }else{ 161 | d->blkdev.transport = SERIAL_UNKNOWN; 162 | } 163 | break; 164 | default: 165 | diag("Unknown transport type %hu on %s\n", maj, d->name); 166 | break; 167 | } 168 | if((d->blkdev.rotation = buf[NMRR]) == 1){ 169 | d->blkdev.rotation = SSD_ROTATION; // special value for solidstate 170 | }else if(d->blkdev.rotation <= 0x401){ 171 | d->blkdev.rotation = 0; // unknown rate 172 | } 173 | if(ntohs(buf[CMDS_SUPP_0]) & FEATURE_WRITE_CACHE){ 174 | d->blkdev.wcache = !!(ntohs(buf[CMDS_EN_0]) & FEATURE_WRITE_CACHE); 175 | verbf("\t%s write-cache: %s\n", d->name, d->blkdev.wcache ? "Enabled" : "Disabled/not present"); 176 | } 177 | if(ntohs(buf[CMDS_SUPP_2]) & WWN_SUP){ 178 | free(d->blkdev.wwn); 179 | if((d->blkdev.wwn = malloc(17)) == NULL){ 180 | return -1; 181 | } 182 | snprintf(d->blkdev.wwn, 17, "%04x%04x%04x%04x", buf[108], buf[109], buf[110], buf[111]); 183 | } 184 | if(buf[CMDS_SUPP_3] & FEATURE_READWRITEVERIFY){ 185 | if(ntohs(buf[CMDS_EN_3]) & FEATURE_READWRITEVERIFY){ 186 | d->blkdev.rwverify = RWVERIFY_SUPPORTED_ON; 187 | }else{ 188 | d->blkdev.rwverify = RWVERIFY_SUPPORTED_OFF; 189 | } 190 | }else{ 191 | d->blkdev.rwverify = RWVERIFY_UNSUPPORTED; 192 | } 193 | verbf("\t%s read-write-verify: %s\n", d->name, 194 | d->blkdev.rwverify == RWVERIFY_UNSUPPORTED ? "Not present" : 195 | d->blkdev.rwverify == RWVERIFY_SUPPORTED_OFF ? "Disabled" : "Enabled"); 196 | for(n = START_SERIAL ; n < START_SERIAL + LENGTH_SERIAL ; ++n){ 197 | buf[n] = ntohs(buf[n]); 198 | } 199 | free(d->blkdev.serial); 200 | d->blkdev.serial = cleanup_serial(buf + START_SERIAL, LENGTH_SERIAL * sizeof(*buf)); 201 | if(d->blkdev.serial == NULL){ 202 | return -1; 203 | } 204 | return 0; 205 | } 206 | 207 | // Serial numbers with weird whitespace are surprisingly common. Clean 'em up. 208 | void *cleanup_serial(const void *vserial, size_t snmax) { 209 | char *clean; 210 | size_t snlen = 0; // number of copied characters <= snmax 211 | size_t iter; // iterator in original string 212 | bool inspace = true; // trim out leading/repeated whitespace 213 | if((clean = malloc(snmax + 1)) == NULL){ 214 | return NULL; 215 | } 216 | const char *serial = vserial; 217 | for(iter = 0 ; iter < snmax ; ++iter){ 218 | if(!serial[iter]){ 219 | break; 220 | }else if(isspace(serial[iter]) || !isprint(serial[iter])){ 221 | if(!inspace){ // trims early or repeated whitespace 222 | clean[snlen++] = ' '; 223 | inspace = true; 224 | } 225 | }else{ 226 | clean[snlen++] = serial[iter]; 227 | inspace = false; 228 | } 229 | } 230 | if(inspace && snlen){ // space was at the end, after actual data 231 | --snlen; 232 | } 233 | clean[snlen] = '\0'; 234 | return clean; 235 | } 236 | -------------------------------------------------------------------------------- /src/sg.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_SG 3 | #define GROWLIGHT_SG 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | 11 | struct device; 12 | 13 | // Takes an open file descriptor on the device node 14 | int sg_interrogate(struct device *, int); 15 | 16 | // Take the incoming serial number and trim leading, repeated, or trailing 17 | // whitespace. The serial number may or may not be NUL-terminated (don't blame 18 | // me; it's how the ioctls work). A NUL-terminator must be respected, but if 19 | // none is present in serialmax characters, stop. Returns a heap-allocated 20 | // buffer of not more than serialmax + 1 bytes, containing the NUL-terminated 21 | // and cleaned up serial number. 22 | void *cleanup_serial(const void *serial, size_t serialmax); 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/sha.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | 4 | #include "sha.h" 5 | #include "nettle/sha1.h" 6 | 7 | void sha1(const void* src, size_t len, void* dst){ 8 | struct sha1_ctx ctx; 9 | sha1_init(&ctx); 10 | sha1_update(&ctx, len, src); 11 | sha1_digest(&ctx, SHA1_DIGEST_SIZE, dst); 12 | } 13 | -------------------------------------------------------------------------------- /src/sha.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_SHA 3 | #define GROWLIGHT_SHA 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | void sha1(const void* src, size_t len, void* dst); 10 | 11 | #ifdef __cplusplus 12 | } 13 | #endif 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/smart.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "smart.h" 12 | #include "growlight.h" 13 | 14 | int probe_smart(device *d){ 15 | char path[PATH_MAX]; 16 | SkBool avail, good; 17 | uint64_t kelvin; 18 | SkDisk *sk; 19 | 20 | if(d->layout != LAYOUT_NONE){ 21 | diag("SMART is only available on block devices\n"); 22 | return -1; 23 | } 24 | d->blkdev.smart = -1; 25 | if(snprintf(path, sizeof(path), "/dev/%s", d->name) >= (int)sizeof(path)){ 26 | diag("Bad name: %s\n", d->name); 27 | return -1; 28 | } 29 | sk = NULL; 30 | if(sk_disk_open(path, &sk)){ 31 | verbf("Couldn't probe %s SMART\n", d->name); 32 | return -1; 33 | } 34 | if(sk_disk_smart_is_available(sk, &avail)){ 35 | verbf("Couldn't probe %s SMART availability\n", path); 36 | sk_disk_free(sk); 37 | return -1; 38 | } 39 | if(!avail){ 40 | verbf("SMART is unavailable: %s\n", d->name); 41 | sk_disk_free(sk); 42 | return 0; 43 | } 44 | if(sk_disk_smart_read_data(sk)){ 45 | verbf("Couldn't read %s SMART data\n", d->name); 46 | sk_disk_free(sk); 47 | return -1; 48 | } 49 | if(sk_disk_smart_status(sk, &good) == 0){ 50 | SkSmartOverall overall; 51 | 52 | if(sk_disk_smart_get_overall(sk, &overall)){ 53 | if(good){ 54 | d->blkdev.smart = SK_SMART_OVERALL_GOOD; 55 | }else{ 56 | d->blkdev.smart = SK_SMART_OVERALL_BAD_STATUS; 57 | } 58 | }else{ 59 | d->blkdev.smart = overall; 60 | } 61 | verbf("Disk (%s) SMART status: %d\n", d->name, d->blkdev.smart); 62 | } 63 | if(sk_disk_smart_get_temperature(sk, &kelvin) == 0){ 64 | d->blkdev.celsius = (kelvin - 273150) / 1000; 65 | verbf("Disk (%s) temperature: %ju\n", d->name, d->blkdev.celsius); 66 | } 67 | sk_disk_free(sk); 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /src/smart.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_SMART 3 | #define GROWLIGHT_SMART 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | 11 | int probe_smart(struct device *d); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/ssd.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include "ssd.h" 3 | #include "popen.h" 4 | #include "growlight.h" 5 | 6 | int fstrim(const char *mnt){ 7 | return vspopen_drain("fstrim -v %s",mnt); 8 | } 9 | 10 | int fstrim_dev(device *d){ 11 | unsigned z; 12 | int ret; 13 | 14 | if(!d->mnttype){ 15 | diag("No filesystem on %s\n",d->name); 16 | return -1; 17 | } 18 | if(d->mnt.count){ 19 | diag("%s is in use (%ux) and cannot be wiped\n",d->name,d->mnt.count); 20 | return -1; 21 | } 22 | ret = 0; 23 | for(z = 0 ; z < d->mnt.count ; ++z){ 24 | ret |= fstrim(d->mnt.list[z]); 25 | } 26 | return ret; 27 | } 28 | -------------------------------------------------------------------------------- /src/ssd.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_SSD 3 | #define GROWLIGHT_SSD 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | 11 | // Run the fstrim(8) command on a mounted filesystem 12 | int fstrim(const char *); 13 | 14 | // Run fstrim() on all a device's mounts. 15 | int fstrim_dev(struct device *); 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/stats.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "stats.h" 7 | #include 8 | #include 9 | #include "growlight.h" 10 | 11 | const char PROCFS_DISKSTATS[] = "/proc/diskstats"; 12 | 13 | int read_proc_diskstats(diskstats **stats) { 14 | return read_diskstats(PROCFS_DISKSTATS, stats); 15 | } 16 | 17 | // procfs files can't be mmap()ed, and always advertise a length of 0. They are 18 | // to be open()ed, read() in one go, and close()d. Returns a heap-allocated 19 | // image of the procfs file plus a NUL terminator, setting *buflen to the 20 | // number of bytes read (*not* including the zero byte). An existing, empty 21 | // file will result in a non-NULL return and a *buflen of 0. Any error will 22 | // result in a return of NULL and an undefined *buflen. 23 | // 24 | // Technically, this function will work on any file supporting read(), but 25 | // usual disk files are typically better mmap()ped. 26 | static char * 27 | read_procfs_file(const char *path, size_t *buflen) { 28 | size_t alloclen = 0; 29 | char *buf = NULL; 30 | int fd, terrno; 31 | ssize_t r; 32 | 33 | if((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0){ 34 | return NULL; 35 | } 36 | *buflen = 0; 37 | r = 0; 38 | do{ 39 | *buflen += r; 40 | if(*buflen == alloclen){ 41 | char *tmp = realloc(buf, alloclen + BUFSIZ); 42 | if(tmp == NULL){ 43 | terrno = errno; 44 | free(buf); 45 | close(fd); 46 | errno = terrno; 47 | return NULL; 48 | } 49 | buf = tmp; 50 | alloclen += BUFSIZ; 51 | } 52 | }while((r = read(fd, buf + *buflen, (alloclen - 1) - *buflen)) > 0); 53 | if(r < 0){ 54 | terrno = errno; 55 | diag("Error reading %zu from %s (%s)\n", *buflen, path, strerror(terrno)); 56 | free(buf); 57 | close(fd); 58 | errno = terrno; 59 | return NULL; 60 | } 61 | verbf("Read %zub from %s\n", *buflen, path); 62 | buf[*buflen] = '\0'; 63 | close(fd); 64 | return buf; 65 | } 66 | 67 | // Find the offset one past the end of the current line (presumed to start at 68 | // offset). Offset must be less than or equal to buflen. The returned value 69 | // will never be greater than buflen. 70 | static size_t 71 | find_line_end(const char *buf, size_t offset, size_t buflen) { 72 | while(offset < buflen){ 73 | if(buf[offset] == '\n'){ 74 | ++offset; 75 | break; 76 | } 77 | ++offset; 78 | } 79 | return offset; 80 | } 81 | 82 | static int 83 | add_diskstat(diskstats **stats, int devcount, diskstats *dstat) { 84 | diskstats *tmp = realloc(*stats, sizeof(**stats) * (devcount + 1)); 85 | if(tmp == NULL){ 86 | return -1; 87 | } 88 | memcpy(&tmp[devcount], dstat, sizeof(*dstat)); 89 | *stats = tmp; 90 | return 0; 91 | } 92 | 93 | // Lex the major/minor number and device name. Copy the device name into dstat. 94 | // Returns the number of characters consumed, or -1 on a lexing failure. 95 | static int 96 | lex_diskstats_prefix(const char *sol, const char *eol, diskstats *dstat) { 97 | const char *start = sol; 98 | // Pass any initial whitespace 99 | while(sol < eol && isspace(*sol)){ 100 | ++sol; 101 | } 102 | // Pass major number 103 | while(sol < eol && isdigit(*sol)){ 104 | ++sol; 105 | } 106 | // Should have whitespace now 107 | if(sol == eol || !isspace(*sol)){ 108 | return -1; 109 | } 110 | // Pass said whitespace 111 | do{ 112 | ++sol; 113 | }while(sol < eol && isspace(*sol)); 114 | // Pass minor number 115 | while(sol < eol && isdigit(*sol)){ 116 | ++sol; 117 | } 118 | // Should have whitespace now 119 | if(sol == eol || !isspace(*sol)){ 120 | return -1; 121 | } 122 | // Pass said whitespace 123 | do{ 124 | ++sol; 125 | }while(sol < eol && isspace(*sol)); 126 | unsigned namelen = 0; 127 | while(sol < eol && isgraph(*sol)){ 128 | dstat->name[namelen++] = *sol; 129 | ++sol; 130 | if(namelen >= sizeof(dstat->name)){ // name was too long, aieee 131 | return -1; 132 | } 133 | } 134 | if(namelen == 0){ 135 | return -1; 136 | } 137 | dstat->name[namelen] = '\0'; 138 | return sol - start; 139 | } 140 | 141 | // Lex up a single line from the diskstats file. 142 | static int 143 | lex_diskstats(const char *sol, const char *eol, diskstats *dstat) { 144 | int consumed; 145 | 146 | consumed = lex_diskstats_prefix(sol, eol, dstat); 147 | if(consumed < 0){ 148 | return -1; 149 | } 150 | sol += consumed; 151 | // sectorsRead is f3, sectorsWritten is f7 152 | uintmax_t f1, f2, f3, f4, f5, f6, f7; 153 | consumed = sscanf(sol, "%ju %ju %ju %ju %ju %ju %ju", 154 | &f1, &f2, &f3, &f4, &f5, &f6, &f7); 155 | if(consumed != 7){ 156 | return -1; 157 | } 158 | dstat->total.sectors_read = f3; 159 | dstat->total.sectors_written = f7; 160 | return 0; 161 | } 162 | 163 | int read_diskstats(const char *path, diskstats **stats) { 164 | diskstats *tmpstats; 165 | size_t buflen; 166 | char *buf; 167 | 168 | if((buf = read_procfs_file(path, &buflen)) == NULL){ 169 | return -1; 170 | } 171 | size_t offset = 0; // where our line starts in the file 172 | size_t eol; // points one past last byte of line after find_line_end() 173 | int devices = 0; 174 | *stats = NULL; 175 | tmpstats = NULL; 176 | while((eol = find_line_end(buf, offset, buflen)) > offset){ 177 | diskstats dstat; 178 | memset(&dstat, 0, sizeof(dstat)); 179 | if(lex_diskstats(buf + offset, buf + eol, &dstat)){ 180 | free(tmpstats); 181 | free(buf); 182 | return -1; 183 | } 184 | if(add_diskstat(&tmpstats, devices, &dstat)){ 185 | free(tmpstats); 186 | free(buf); 187 | return -1; 188 | } 189 | ++devices; 190 | offset = eol + 1; 191 | } 192 | free(buf); 193 | *stats = tmpstats; 194 | return devices; 195 | } 196 | -------------------------------------------------------------------------------- /src/stats.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_STATS 3 | #define GROWLIGHT_STATS 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | #include 11 | 12 | // See Linux's documentation/iostats.txt for description of the procfs disk 13 | // statistics. On Linux 4.18+, we have 17 fields: 14 | // 15 | // major minor devname 16 | // readsComp readsMerged sectorsRead msRead 17 | // writesComp writesMerged sectorsWritten msWritten 18 | // iosInProgress msIOs weightedmsIOs 19 | // discardsComp discardsMerged sectorsDiscarded msDiscarded 20 | // 21 | // Prior to 4.18, the last four fields were not present. 22 | typedef struct statpack { 23 | uint64_t sectors_read; 24 | uint64_t sectors_written; 25 | } statpack; 26 | 27 | typedef struct diskstats { 28 | char name[NAME_MAX + 1]; 29 | statpack total; 30 | } diskstats; 31 | 32 | // Reads the entirety of /proc/diskstats, and copies the results we care about 33 | // into a heap-allocated array of stats objects. We use /proc/diskstats because 34 | // we'd otherwise need open a sysfs file per partition/block device. The return 35 | // value is the number of entries in *stats. *stats is NULL iff the return 36 | // value is less than or equal to 0. An error results in a negative return. 37 | int read_proc_diskstats(diskstats **stats); 38 | 39 | // Allows the path to be specified. 40 | int read_diskstats(const char *path, diskstats **stats); 41 | 42 | #ifdef __cplusplus 43 | } 44 | #endif 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/swap.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "swap.h" 11 | #include "popen.h" 12 | #include "growlight.h" 13 | 14 | int mkswap(device *d){ 15 | if(d->mnttype && strcmp(d->mnttype, "swap")){ 16 | diag("Won't create swap on %s filesystem at %s\n", 17 | d->mnttype, d->name); 18 | return -1; 19 | } 20 | if(d->swapprio >= SWAP_MAXPRIO){ 21 | diag("Already swapping on %s\n", d->name); 22 | return -1; 23 | } 24 | if(vspopen_drain("mkswap -L SprezzaSwap /dev/%s", d->name)){ 25 | return -1; 26 | } 27 | return 0; 28 | } 29 | 30 | // Create swap on the device, and use it 31 | int swapondev(device *d){ 32 | char fn[PATH_MAX], *mt; 33 | 34 | if(mkswap(d)){ 35 | return -1; 36 | } 37 | snprintf(fn, sizeof(fn), "/dev/%s", d->name); 38 | if((mt = strdup("swap")) == NULL){ 39 | return -1; 40 | } 41 | if(swapon(fn, 0)){ 42 | diag("Couldn't swap on %s (%s?)\n", fn, strerror(errno)); 43 | free(mt); 44 | return -1; 45 | } 46 | free(d->mnttype); 47 | d->mnttype = mt; 48 | d->swapprio = SWAP_MAXPRIO; // FIXME take as param 49 | return 0; 50 | } 51 | 52 | // Deactive the swap on this partition (if applicable) 53 | int swapoffdev(device *d){ 54 | char fn[PATH_MAX]; 55 | 56 | snprintf(fn, sizeof(fn), "/dev/%s", d->name); 57 | if(swapoff(fn)){ 58 | diag("Couldn't stop swapping on %s (%s?)\n", fn, strerror(errno)); 59 | return -1; 60 | } 61 | d->swapprio = SWAP_INACTIVE; 62 | return 0; 63 | } 64 | 65 | // Parse /proc/swaps to detect active swap devices 66 | int parse_swaps(const glightui *gui, const char *name){ 67 | char buf[BUFSIZ]; 68 | int line = 0; 69 | FILE *fp; 70 | 71 | if((fp = fopen(name, "re")) == NULL){ 72 | diag("Couldn't open %s (%s?)\n", name, strerror(errno)); 73 | return -1; 74 | } 75 | // First line is a legend 76 | while(fgets(buf, sizeof(buf), fp)){ 77 | char *toke = buf, *type, *size, *e; 78 | device *d; 79 | 80 | if(++line == 1){ 81 | continue; 82 | } 83 | while(isgraph(*toke)){ // First field: "Filename" 84 | ++toke; 85 | } 86 | *toke++ = '\0'; 87 | while(isspace(*toke)){ 88 | ++toke; 89 | } 90 | type = toke++; // Second field: "Type". "block"/"file" 91 | while(isgraph(*toke)){ 92 | ++toke; 93 | } 94 | *toke++ = '\0'; 95 | if(strcmp(type, "file") == 0){ 96 | continue; 97 | } 98 | if((d = lookup_device(buf)) == NULL){ 99 | goto err; 100 | } 101 | size = toke++; // Third field: "Size". 102 | while(isgraph(*toke)){ 103 | ++toke; 104 | } 105 | *toke++ = '\0'; 106 | errno = 0; 107 | if(((d->mntsize = strtoull(size, &e, 0)) == ULLONG_MAX && errno == ERANGE) || 108 | d->mntsize == 0 || *e){ 109 | goto err; 110 | } 111 | d->mntsize *= 1024; 112 | if(d->swapprio == SWAP_INVALID){ 113 | if(!d->mnttype || strcmp(d->mnttype, "swap")){ 114 | if(d->mnttype){ 115 | diag("Warning: %s went from %s to swap\n", d->name, d->mnttype); 116 | free(d->mnttype); 117 | d->mnttype = NULL; 118 | } 119 | if((d->mnttype = strdup("swap")) == NULL){ 120 | goto err; 121 | } 122 | } 123 | // FIXME we can get the real priority from the last field 124 | d->swapprio = SWAP_MAXPRIO; // FIXME 125 | if(d->layout == LAYOUT_PARTITION){ 126 | d = d->partdev.parent; 127 | } 128 | d->uistate = gui->block_event(d, d->uistate); 129 | } 130 | } 131 | fclose(fp); 132 | return 0; 133 | 134 | err: 135 | diag("Error parsing %s\n", name); 136 | fclose(fp); 137 | return -1; 138 | } 139 | -------------------------------------------------------------------------------- /src/swap.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_SWAP 3 | #define GROWLIGHT_SWAP 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | struct growlight_ui; 11 | 12 | // Create swap on the device, and use it 13 | int swapondev(struct device *); 14 | 15 | // Deactive the swap on this partition (if applicable) 16 | int swapoffdev(struct device *); 17 | 18 | // Parse /proc/swaps to detect active swap devices 19 | int parse_swaps(const struct growlight_ui *,const char *); 20 | 21 | #ifdef __cplusplus 22 | } 23 | #endif 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/sysfs.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "sysfs.h" 13 | #include "growlight.h" 14 | 15 | // FIXME use libudev or at least mmap.c for this crap 16 | // FIXME sysfs is UTF-8 not ASCII! 17 | char *get_sysfs_string(int dirfd,const char *node){ 18 | char buf[512]; // FIXME 19 | ssize_t r; 20 | int fd; 21 | 22 | if((fd = openat(dirfd,node,O_RDONLY|O_NONBLOCK|O_CLOEXEC)) < 0){ 23 | return NULL; 24 | } 25 | if((r = read(fd,buf,sizeof(buf))) <= 0){ 26 | int e = errno; 27 | close(fd); 28 | errno = e; 29 | return NULL; 30 | } 31 | if((size_t)r >= sizeof(buf) || buf[r - 1] != '\n'){ 32 | close(fd); 33 | errno = ENAMETOOLONG; 34 | return NULL; 35 | } 36 | close(fd); 37 | // Sometimes the sysfs entry has a bunch of spaces at the end, ugh 38 | while(isspace(buf[r - 1])){ 39 | buf[r - 1] = '\0'; 40 | if(--r == 0){ // huh 41 | return NULL; 42 | } 43 | } 44 | return strdup(buf); 45 | } 46 | 47 | int sysfs_devno(int dirfd,dev_t *devno){ 48 | int fd = openat(dirfd,"dev",O_RDONLY|O_NONBLOCK|O_CLOEXEC); 49 | const char *colon; 50 | char buf[512]; // FIXME 51 | ssize_t r; 52 | 53 | if(fd < 0){ 54 | return -1; 55 | } 56 | if((r = read(fd,buf,sizeof(buf))) <= 0){ 57 | int e = errno; 58 | close(fd); 59 | errno = e; 60 | return -1; 61 | } 62 | if((size_t)r >= sizeof(buf) || buf[r - 1] != '\n'){ 63 | close(fd); 64 | errno = ENAMETOOLONG; 65 | return -1; 66 | } 67 | close(fd); 68 | if((colon = strchr(buf,':')) == NULL){ 69 | return -1; 70 | } 71 | *devno = MKDEV(atoi(buf),atoi(colon + 1)); 72 | return 0; 73 | } 74 | 75 | unsigned sysfs_exist_p(int dirfd,const char *node){ 76 | int fd; 77 | 78 | if((fd = openat(dirfd,node,O_RDONLY|O_NONBLOCK|O_CLOEXEC)) < 0){ 79 | return 0; 80 | } 81 | close(fd); 82 | return 1; 83 | } 84 | 85 | int get_sysfs_bool(int dirfd,const char *node,unsigned *b){ 86 | char buf[512]; // FIXME 87 | ssize_t r; 88 | int fd; 89 | 90 | if((fd = openat(dirfd,node,O_RDONLY|O_NONBLOCK|O_CLOEXEC)) < 0){ 91 | return -1; 92 | } 93 | if((r = read(fd,buf,sizeof(buf))) <= 0){ 94 | int e = errno; 95 | close(fd); 96 | errno = e; 97 | return -1; 98 | } 99 | if((size_t)r >= sizeof(buf) || buf[r - 1] != '\n'){ 100 | close(fd); 101 | errno = ENAMETOOLONG; 102 | return -1; 103 | } 104 | close(fd); 105 | buf[r - 1] = '\0'; 106 | *b = strcmp(buf,"0") ? 1 : 0; 107 | return 0; 108 | } 109 | 110 | int get_sysfs_uint(int dirfd,const char *node,unsigned long *b){ 111 | char *end,buf[512]; // FIXME 112 | ssize_t r; 113 | int fd; 114 | 115 | if((fd = openat(dirfd,node,O_RDONLY|O_NONBLOCK|O_CLOEXEC)) < 0){ 116 | return -1; 117 | } 118 | if((r = read(fd,buf,sizeof(buf))) <= 0){ 119 | int e = errno; 120 | close(fd); 121 | errno = e; 122 | return -1; 123 | } 124 | if((size_t)r >= sizeof(buf) || buf[r - 1] != '\n'){ 125 | close(fd); 126 | errno = ENAMETOOLONG; 127 | return -1; 128 | } 129 | close(fd); 130 | buf[r - 1] = '\0'; 131 | *b = strtoul(buf,&end,0); 132 | if(*end){ 133 | diag("Malformed sysfs uint: %s\n",buf); 134 | return -1; 135 | } 136 | return 0; 137 | } 138 | 139 | int get_sysfs_int(int dirfd,const char *node,int *b){ 140 | char *end,buf[512]; // FIXME 141 | ssize_t r; 142 | long ll; 143 | int fd; 144 | 145 | if((fd = openat(dirfd,node,O_RDONLY|O_NONBLOCK|O_CLOEXEC)) < 0){ 146 | return -1; 147 | } 148 | if((r = read(fd,buf,sizeof(buf))) <= 0){ 149 | int e = errno; 150 | close(fd); 151 | errno = e; 152 | return -1; 153 | } 154 | if((size_t)r >= sizeof(buf) || buf[r - 1] != '\n'){ 155 | close(fd); 156 | errno = ENAMETOOLONG; 157 | return -1; 158 | } 159 | close(fd); 160 | buf[r - 1] = '\0'; 161 | ll = strtol(buf,&end,0); 162 | if(ll > INT_MAX){ 163 | diag("Invalid sysfs int: %s\n",buf); 164 | return -1; 165 | } 166 | *b = ll; 167 | if(*end){ 168 | diag("Malformed sysfs uint: %s\n",buf); 169 | return -1; 170 | } 171 | return 0; 172 | } 173 | 174 | int write_sysfs(const char *name,const char *str){ 175 | ssize_t w; 176 | int fd; 177 | 178 | if((fd = open(name,O_WRONLY|O_NONBLOCK|O_CLOEXEC)) < 0){ 179 | return -1; 180 | } 181 | if((w = write(fd,str,strlen(str))) <= 0 || w < (int)strlen(str)){ 182 | int e = errno; 183 | close(fd); 184 | errno = e; 185 | return -1; 186 | } 187 | close(fd); 188 | return 0; 189 | } 190 | -------------------------------------------------------------------------------- /src/sysfs.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_SYSFS 3 | #define GROWLIGHT_SYSFS 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | 11 | int sysfs_devno(int,dev_t *); 12 | unsigned sysfs_exist_p(int,const char *); 13 | char *get_sysfs_string(int,const char *); 14 | int get_sysfs_bool(int,const char *,unsigned *); 15 | int get_sysfs_int(int,const char *,int *); 16 | int get_sysfs_uint(int,const char *,unsigned long *); 17 | int write_sysfs(const char *,const char *); 18 | 19 | #ifdef __cplusplus 20 | } 21 | #endif 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/target.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "popen.h" 13 | #include "target.h" 14 | #include "growlight.h" 15 | 16 | // Path on the guest filesystem which will hold the target's root filesystem. 17 | const char *growlight_target = NULL; 18 | char real_target[PATH_MAX + 1]; // Only used when we set or unset the target 19 | 20 | static int targfd = -1; // reference to target root, once defined 21 | 22 | static char * 23 | dump_controller_modules(void){ 24 | const controller *c; 25 | char *out, *tmp; 26 | size_t off; 27 | 28 | off = 0; 29 | if((out = malloc(sizeof(*out) * (off + 1))) == NULL){ 30 | return NULL; 31 | } 32 | out[0] = '\0'; 33 | if(targfd < 0){ 34 | return out; 35 | } 36 | for(c = get_controllers() ; c ; c = c->next){ 37 | if(strstr(out, c->driver)){ 38 | continue; 39 | } 40 | if((tmp = realloc(out, sizeof(*out) * (strlen(c->driver) + off + 1))) == NULL){ 41 | goto err; 42 | } 43 | out = tmp; 44 | snprintf(out + off, strlen(c->driver) + 1, "%s\n", c->driver); 45 | off += strlen(c->driver) + 1; 46 | } 47 | return out; 48 | 49 | err: 50 | free(out); 51 | return NULL; 52 | } 53 | 54 | static void 55 | use_new_target(const char *path){ 56 | const controller *c; 57 | 58 | for(c = get_controllers() ; c ; c = c->next){ 59 | device *d, *p; 60 | 61 | for(d = c->blockdevs ; d ; d = d->next){ 62 | for(p = d->parts ; p ; p = p->next){ 63 | if(string_included_p(&p->mnt, path)){ 64 | mount_target(); 65 | } 66 | } 67 | if(string_included_p(&d->mnt, path)){ 68 | mount_target(); 69 | } 70 | } 71 | } 72 | } 73 | 74 | const char TARGETINFO[] = "Growlight will attempt to prepare a bootable system " 75 | "at %s. At minimum, a target root filesystem must be mounted at this location, " 76 | "so that /etc/fstab and other files can be prepared. The target root must not " 77 | "be mounted with any of the ro, nodev, noexec or nosuid options. " 78 | "You must target a root filesystem first; " 79 | "having done so, you can set up other targets underneath it. Some typical " 80 | "subtargets, none of them required, include /usr/local (so that it can be " 81 | "preserved across installs/machines), /home (for the same reason, and so that " 82 | "nosuid/nodev and/or encryption can be applied), and /var (to protect against " 83 | "its arbitrary growth). Any variety of filesystem can be targeted, but " 84 | "bootloaders typically have their own requirements. Some BIOS firmwares will " 85 | "not attempt to boot from a hard drive lacking an MSDOS partition table, or a " 86 | "partition marked with the bootable flag. UEFI requires a GPT table and an " 87 | "EFI System Partition. Once your targets are configured, finalize the " 88 | "appropriate configuration (one of UEFI, BIOS, or no firmware). Any swap that " 89 | "is enabled will be configured for use on the target machine."; 90 | 91 | int set_target(const char *path){ 92 | if(path){ 93 | if(growlight_target){ 94 | diag("A target is already defined: %s\n", growlight_target); 95 | return -1; 96 | } 97 | if(realpath(path, real_target) == NULL){ 98 | diag("Couldn't resolve %s (%s?)\n", path, strerror(errno)); 99 | return -1; 100 | } 101 | get_glightui()->boxinfo(TARGETINFO, real_target); 102 | use_new_target(real_target); 103 | growlight_target = real_target; 104 | return 0; 105 | } 106 | if(!growlight_target){ 107 | diag("No target is defined\n"); 108 | return -1; 109 | } 110 | growlight_target = NULL; 111 | unmount_target(); 112 | return 0; 113 | } 114 | 115 | int finalize_target(void){ 116 | char pathext[PATH_MAX + 1], *fstab, *ftargs; 117 | FILE *fp; 118 | int fd, r; 119 | 120 | if(!growlight_target){ 121 | diag("No target is defined\n"); 122 | return -1; 123 | } 124 | if(targfd < 0){ 125 | diag("No target mappings are defined\n"); 126 | return -1; 127 | } 128 | if((unsigned)snprintf(pathext, sizeof(pathext), "%s/etc", growlight_target) >= sizeof(pathext)){ 129 | diag("Name too long (%s/etc)\n", growlight_target); 130 | return -1; 131 | } 132 | if(mkdir(pathext, 0755) && errno != EEXIST){ 133 | diag("Couldn't mkdir %s (%s?)\n", pathext, strerror(errno)); 134 | return -1; 135 | } 136 | if((unsigned)snprintf(pathext, sizeof(pathext), "%s/etc/initramfs-tools", growlight_target) >= sizeof(pathext)){ 137 | diag("Name too long (%s/etc/initramfs-tools)\n", growlight_target); 138 | return -1; 139 | } 140 | if(mkdir(pathext, 0755) && errno != EEXIST){ 141 | diag("Couldn't mkdir %s (%s?)\n", pathext, strerror(errno)); 142 | return -1; 143 | } 144 | if((fd = openat(targfd, "etc/fstab", O_WRONLY|O_CLOEXEC|O_CREAT, 145 | S_IROTH | S_IRGRP | S_IWGRP | S_IRUSR | S_IWUSR)) < 0){ 146 | diag("Couldn't open etc/fstab in target root (%s?)\n", strerror(errno)); 147 | return -1; 148 | } 149 | if((fp = fdopen(fd, "w")) == NULL){ 150 | diag("Couldn't get FILE * from %d (%s?)\n", fd, strerror(errno)); 151 | close(fd); 152 | return -1; 153 | } 154 | if((fstab = dump_targets()) == NULL){ 155 | diag("Couldn't write targets to %s/etc/fstab (%s?)\n", growlight_target, strerror(errno)); 156 | fclose(fp); 157 | return -1; 158 | } 159 | if((r = fprintf(fp, "%s", fstab)) < 0 || (size_t)r < strlen(fstab)){ 160 | diag("Couldn't write data to %s/etc/fstab (%s?)\n", growlight_target, strerror(errno)); 161 | free(fstab); 162 | fclose(fp); 163 | return -1; 164 | } 165 | free(fstab); 166 | if(fclose(fp)){ 167 | diag("Couldn't close FILE * from %d (%s?)\n", fd, strerror(errno)); 168 | close(fd); 169 | return -1; 170 | } 171 | if((fd = openat(targfd, "etc/initramfs-tools/modules", 172 | O_WRONLY|O_CLOEXEC|O_CREAT, 173 | S_IROTH | S_IRGRP | S_IWGRP | S_IRUSR | S_IWUSR)) < 0){ 174 | diag("Couldn't open etc/initramfs-tools/modules in target root (%s?)\n", strerror(errno)); 175 | return -1; 176 | } 177 | if((fp = fdopen(fd, "w")) == NULL){ 178 | diag("Couldn't get FILE * from %d (%s?)\n", fd, strerror(errno)); 179 | close(fd); 180 | return -1; 181 | } 182 | if((ftargs = dump_controller_modules()) == NULL){ 183 | diag("Couldn't write targets to %s/etc/initramfs-tools/modules (%s?)\n", growlight_target, strerror(errno)); 184 | fclose(fp); 185 | return -1; 186 | } 187 | if((r = fprintf(fp, "%s", ftargs)) < 0 || (size_t)r < strlen(ftargs)){ 188 | diag("Couldn't write data to %s/etc/initramfs-tools/modules (%s?)\n", growlight_target, strerror(errno)); 189 | free(ftargs); 190 | fclose(fp); 191 | return -1; 192 | } 193 | free(ftargs); 194 | if(fclose(fp)){ 195 | diag("Couldn't close FILE * from %d (%s?)\n", fd, strerror(errno)); 196 | close(fd); 197 | return -1; 198 | } 199 | finalized = 1; 200 | return 0; 201 | } 202 | 203 | // FIXME prefer labels or UUIDs for identification! 204 | static const char * 205 | fstab_name(const device *d){ 206 | return d->name; 207 | } 208 | 209 | static char * 210 | dump_device_targets(char *s, const device *d){ 211 | unsigned z; 212 | char *tmp; 213 | int r; 214 | 215 | // ZFS maintains its own mountpoint tracking, external to /etc/fstab 216 | if(!d->mnttype){ 217 | return s; 218 | } 219 | if(strcmp(d->mnttype, "zfs") == 0){ 220 | return s; 221 | } 222 | for(z = 0 ; z < d->mnt.count ; ++z){ 223 | const char *mnt; 224 | int dump; 225 | 226 | // Don't write mounts external to the target 227 | if(strncmp(d->mnt.list[z], growlight_target, strlen(growlight_target))){ 228 | continue; 229 | } 230 | if(strcmp(d->mnt.list[z], growlight_target) == 0){ 231 | mnt = "/"; 232 | dump = 1; 233 | }else{ 234 | mnt = d->mnt.list[z] + strlen(growlight_target); 235 | dump = 2; 236 | } 237 | r = snprintf(NULL, 0, "/dev/%s %s %s %s 0 %u\n", fstab_name(d), 238 | mnt, d->mnttype, d->mntops.list[z], dump); 239 | if((tmp = realloc(s, sizeof(*s) * (strlen(s) + r + 1))) == NULL){ 240 | goto err; 241 | } 242 | s = tmp; 243 | sprintf(s + strlen(s), "/dev/%s %s %s %s 0 %u\n", fstab_name(d), 244 | mnt, d->mnttype, d->mntops.list[z], dump); 245 | } 246 | if(d->swapprio != SWAP_INVALID){ 247 | r = snprintf(NULL, 0, "/dev/%s\tnone\t\t%s\n", fstab_name(d), d->mnttype); 248 | if((tmp = realloc(s, sizeof(*s) * (strlen(s) + r + 1))) == NULL){ 249 | goto err; 250 | } 251 | s = tmp; 252 | sprintf(s + strlen(s), "/dev/%s\tnone\t\t%s\n", fstab_name(d), d->mnttype); 253 | } 254 | return s; 255 | 256 | err: 257 | return NULL; 258 | } 259 | 260 | char *dump_targets(void){ 261 | const controller *c; 262 | char *out, *tmp; 263 | size_t off; 264 | int z; 265 | 266 | off = 0; 267 | if((out = malloc(sizeof(*out) * (off + 1))) == NULL){ 268 | return NULL; 269 | } 270 | out[0] = '\0'; 271 | if(targfd < 0){ 272 | return out; 273 | } 274 | // FIXME allow various naming schemes 275 | for(c = get_controllers() ; c ; c = c->next){ 276 | const device *d; 277 | 278 | for(d = c->blockdevs ; d ; d = d->next){ 279 | const device *p; 280 | 281 | if(d->layout == LAYOUT_NONE && d->blkdev.removable){ 282 | // FIXME differentiate USB etc 283 | z = snprintf(NULL, 0, "/dev/%s\t%s\t%s\t%s\t0\t0\n", fstab_name(d), 284 | "/media/cdrom", "auto", "noauto, user"); 285 | if((tmp = realloc(out, sizeof(*out) * (z + off + 1))) == NULL){ 286 | goto err; 287 | } 288 | out = tmp; 289 | sprintf(out + off, "/dev/%s\t%s\t%s\t%s\t0\t0\n", fstab_name(d), 290 | "/media/cdrom", "auto", "noauto, user"); 291 | off += z; 292 | }else{ 293 | if((tmp = dump_device_targets(out, d)) == NULL){ 294 | goto err; 295 | } 296 | out = tmp; 297 | off += strlen(out + off); 298 | } 299 | for(p = d->parts ; p ; p = p->next){ 300 | if((tmp = dump_device_targets(out, p)) == NULL){ 301 | goto err; 302 | } 303 | out = tmp; 304 | off += strlen(out + off); 305 | } 306 | } 307 | } 308 | #define PROCLINE "proc\t\t/proc\t\tproc\tdefaults\t0\t0\n" 309 | if((tmp = realloc(out, sizeof(*out) * (off + strlen(PROCLINE) + 1))) == NULL){ 310 | goto err; 311 | } 312 | out = tmp; 313 | sprintf(out + off, "%s", PROCLINE); 314 | #undef PROCLINE 315 | return out; 316 | 317 | err: 318 | free(out); 319 | return NULL; 320 | } 321 | 322 | int mount_target(void){ 323 | if(targfd >= 0){ 324 | verbf("Targfd already opened at %d\n", targfd); 325 | close(targfd); 326 | } 327 | if((targfd = open(growlight_target, O_DIRECTORY|O_RDONLY|O_CLOEXEC)) < 0){ 328 | diag("Couldn't open %s (%s?)\n", growlight_target, strerror(errno)); 329 | return -1; 330 | } 331 | return 0; 332 | } 333 | 334 | int unmount_target(void){ 335 | if(targfd < 0){ 336 | verbf("Targfd wasn't open\n"); 337 | return -1; 338 | } 339 | if(close(targfd)){ 340 | diag("Error closing targfd %d (%s)\n", targfd, strerror(errno)); 341 | targfd = -1; 342 | return -1; 343 | } 344 | targfd = -1; 345 | return 0; 346 | } 347 | -------------------------------------------------------------------------------- /src/target.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_TARGET 3 | #define GROWLIGHT_TARGET 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | 11 | struct device; 12 | 13 | int set_target(const char *); 14 | int finalize_target(void); 15 | char *dump_targets(void); 16 | 17 | // don't muck around with me externally. use set_target() and get_target(). 18 | extern const char *growlight_target; 19 | 20 | static inline const char * 21 | get_target(void){ 22 | return growlight_target; 23 | } 24 | 25 | // Indicate that we've just mounted/unmounted the target root 26 | int mount_target(void); 27 | int unmount_target(void); 28 | 29 | #ifdef __cplusplus 30 | } 31 | #endif 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/threads.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include "threads.h" 3 | 4 | // initialize a recursive mutex lock in a way that works on both glibc + musl 5 | int recursive_lock_init(pthread_mutex_t *lock){ 6 | #ifndef __GLIBC__ 7 | #define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE 8 | #endif 9 | pthread_mutexattr_t attr; 10 | if(pthread_mutexattr_init(&attr)){ 11 | return -1; 12 | } 13 | if(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP)){ 14 | pthread_mutexattr_destroy(&attr); 15 | return -1; 16 | } 17 | if(pthread_mutex_init(lock, &attr)){ 18 | pthread_mutexattr_destroy(&attr); 19 | return -1; 20 | } 21 | pthread_mutexattr_destroy(&attr); 22 | return 0; 23 | #ifndef __GLIBC__ 24 | #undef PTHREAD_MUTEX_RECURSIVE_NP 25 | #endif 26 | } 27 | -------------------------------------------------------------------------------- /src/threads.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_THREADS 3 | #define GROWLIGHT_THREADS 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include 10 | 11 | int recursive_lock_init(pthread_mutex_t *lock); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/udev.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "zfs.h" 10 | #include "udev.h" 11 | #include "pthread.h" 12 | #include "growlight.h" 13 | 14 | static struct udev *udev; 15 | struct udev_monitor *udmon; 16 | 17 | int udev_event(const glightui *gui){ 18 | struct udev_device *dev; 19 | 20 | while( (dev = udev_monitor_receive_device(udmon)) ){ 21 | const char *subsys = udev_device_get_subsystem(dev); 22 | verbf("udev: %s %s %s %s %s %s %s\n", 23 | udev_device_get_devpath(dev), subsys, 24 | udev_device_get_devtype(dev), udev_device_get_syspath(dev), 25 | udev_device_get_sysname(dev), udev_device_get_sysnum(dev), 26 | udev_device_get_devnode(dev)); 27 | if(strcmp(subsys, "bdi") == 0){ 28 | scan_zpools(gui); 29 | }else{ 30 | rescan_device(udev_device_get_sysname(dev)); 31 | } 32 | } 33 | return 0; 34 | } 35 | 36 | int monitor_udev(void){ 37 | int r; 38 | 39 | if((udev = udev_new()) == NULL){ 40 | diag("Couldn't get udev instance (%s?)\n", strerror(errno)); 41 | return -1; 42 | } 43 | if((udmon = udev_monitor_new_from_netlink(udev, "udev")) == NULL){ 44 | diag("Couldn't get udev monitor (%s?)\n", strerror(errno)); 45 | udev_unref(udev); 46 | return -1; 47 | } 48 | if(udev_monitor_filter_add_match_subsystem_devtype(udmon, "bdi", NULL)){ 49 | diag("Warning: couldn't watch bdi events\n"); 50 | } 51 | if(udev_monitor_filter_add_match_subsystem_devtype(udmon, "block", NULL)){ 52 | diag("Couldn't watch block events\n"); 53 | udev_monitor_unref(udmon); 54 | udev_unref(udev); 55 | return -1; 56 | } 57 | if(udev_monitor_enable_receiving(udmon)){ 58 | diag("Couldn't enable udev\n"); 59 | udev_monitor_unref(udmon); 60 | udev_unref(udev); 61 | return -1; 62 | } 63 | if((r = udev_monitor_get_fd(udmon)) < 0){ 64 | diag("Couldn't get udev fd\n"); 65 | udev_monitor_unref(udmon); 66 | udev_unref(udev); 67 | return -1; 68 | } 69 | return r; 70 | } 71 | 72 | int shutdown_udev(void){ 73 | diag("Shutting down udev monitor...\n"); 74 | udev_monitor_unref(udmon); 75 | udev_unref(udev); 76 | udmon = NULL; 77 | udev = NULL; 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /src/udev.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_UDEV 3 | #define GROWLIGHT_UDEV 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #include "growlight.h" 10 | 11 | int monitor_udev(void); 12 | int udev_event(const glightui *); 13 | int shutdown_udev(void); 14 | 15 | #ifdef __cplusplus 16 | } 17 | #endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/version.h.in: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #define growlight_VERSION_MAJOR "@growlight_VERSION_MAJOR@" 3 | #define growlight_VERSION_MINOR "@growlight_VERSION_MINOR@" 4 | #define growlight_VERSION_PATCH "@growlight_VERSION_PATCH@" 5 | #define VERSION growlight_VERSION_MAJOR "." growlight_VERSION_MINOR "." growlight_VERSION_PATCH 6 | #define PACKAGE "growlight" 7 | #define GROWLIGHT_SHARE "@CMAKE_INSTALL_FULL_DATADIR@/" PACKAGE 8 | #cmakedefine USE_LIBZFS 9 | -------------------------------------------------------------------------------- /src/zfs.c: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "zfs.h" 9 | #include "popen.h" 10 | #include "growlight.h" 11 | 12 | #ifdef USE_LIBZFS 13 | #include 14 | 15 | // FIXME hacks around the libspl/libzfs autotools-dominated jank 16 | #define ulong_t unsigned long 17 | #define boolean_t bool 18 | #include 19 | //#include 20 | 21 | static libzfs_handle_t *zht; 22 | // FIXME taken from zpool.c source 23 | static char props[] = "name,size,allocated,free,capacity,dedupratio,health,altroot"; 24 | 25 | struct zpoolcb_t { 26 | unsigned pools; 27 | const glightui *gui; 28 | }; 29 | 30 | static uintmax_t 31 | dehumanize(const char *num){ 32 | unsigned long long ull, dec; 33 | char *e; 34 | 35 | ull = strtoul(num, &e, 0); 36 | if(*e == '.'){ 37 | dec = strtoul(++e, &e, 0); 38 | }else{ 39 | dec = 0; 40 | } 41 | switch(*e){ 42 | case 'E': case 'e': ull = ull * 1000 + dec * 10; dec = 0; __attribute__((__fallthrough__)); 43 | case 'P': case 'p': ull = ull * 1000 + dec * 10; dec = 0; __attribute__((__fallthrough__)); 44 | case 'T': case 't': ull = ull * 1000 + dec * 10; dec = 0; __attribute__((__fallthrough__)); 45 | case 'G': case 'g': ull = ull * 1000 + dec * 10; dec = 0; __attribute__((__fallthrough__)); 46 | case 'M': case 'm': ull = ull * 1000 + dec * 10; dec = 0; __attribute__((__fallthrough__)); 47 | case 'K': case 'k': ull = ull * 1000 + dec * 10; dec = 0; 48 | break; 49 | case '\0': 50 | if(dec){ 51 | diag("Couldn't convert dec: %s\n", num); 52 | ull = 0; 53 | } 54 | break; 55 | default: 56 | diag("Couldn't convert number: %s\n", num); 57 | ull = 0; 58 | } 59 | return ull; 60 | } 61 | 62 | static int 63 | zpoolcb(zpool_handle_t *zhp, void *arg){ 64 | char size[10], guid[21], ashift[5], health[20]; 65 | struct zpoolcb_t *cb = arg; 66 | const glightui *gui; 67 | uint64_t version; 68 | const char *name; 69 | nvlist_t *conf; 70 | device *d; 71 | int state; 72 | 73 | gui = cb->gui; 74 | name = zpool_get_name(zhp); 75 | conf = zpool_get_config(zhp, NULL); 76 | if(!name || !conf){ 77 | diag("name/config failed for zpool\n"); 78 | zpool_close(zhp); 79 | return -1; 80 | } 81 | if(strlen(name) >= sizeof(d->name)){ 82 | diag("zpool name too long: %s\n", name); 83 | zpool_close(zhp); 84 | return -1; 85 | } 86 | ++cb->pools; 87 | if(nvlist_lookup_uint64(conf, ZPOOL_CONFIG_VERSION, &version)){ 88 | diag("Couldn't get %s zpool version\n", name); 89 | zpool_close(zhp); 90 | return -1; 91 | } 92 | if(version > SPA_VERSION){ 93 | diag("%s zpool version too new (%lu > %llu)\n", 94 | name, version, SPA_VERSION); 95 | zpool_close(zhp); 96 | return -1; 97 | } 98 | state = zpool_get_state(zhp); 99 | if(zpool_get_prop(zhp, ZPOOL_PROP_HEALTH, health, sizeof(health), NULL, true)){ 100 | diag("Couldn't get health for %s\n", name); 101 | zpool_close(zhp); 102 | return -1; 103 | } 104 | if(strcmp(health, "FAULTED")){ // FIXME really?!?! 105 | if(zpool_get_prop(zhp, ZPOOL_PROP_SIZE, size, sizeof(size), NULL, true)){ 106 | diag("Couldn't get size for zpool '%s'\n", name); 107 | zpool_close(zhp); 108 | return -1; 109 | } 110 | if(zpool_get_prop(zhp, ZPOOL_PROP_ASHIFT, ashift, sizeof(ashift), NULL, true)){ 111 | diag("Couldn't get ashift for zpool '%s'\n", name); 112 | zpool_close(zhp); 113 | return -1; 114 | } 115 | }else{ 116 | verbf("Assuming zero size for faulted zpool '%s'\n", name); 117 | strcpy(ashift, "0"); 118 | strcpy(size, "0"); 119 | } 120 | if(zpool_get_prop(zhp, ZPOOL_PROP_GUID, guid, sizeof(guid), NULL, true)){ 121 | diag("Couldn't get GUID for zpool '%s'\n", name); 122 | zpool_close(zhp); 123 | return -1; 124 | } 125 | lock_growlight(); 126 | if( (d = lookup_device(name)) ){ 127 | if(d->layout != LAYOUT_ZPOOL){ 128 | diag("Zpool %s collided with %s\n", name, d->name); 129 | zpool_close(zhp); 130 | return -1; 131 | } 132 | if(strcmp(d->uuid, guid)){ 133 | diag("UUID changed on %s\n", name); 134 | free(d->uuid); 135 | } 136 | d->uuid = strdup(guid); 137 | if(d->size != dehumanize(size)){ 138 | diag("Size changed on %s (%ju->%s)\n", name, d->size, size); 139 | } 140 | d->size = dehumanize(size); 141 | d->zpool.state = state; 142 | d->zpool.zpoolver = version; 143 | zpool_close(zhp); 144 | d->uistate = gui->block_event(d, d->uistate); 145 | unlock_growlight(); 146 | return 0; 147 | } 148 | unlock_growlight(); 149 | if((d = malloc(sizeof(*d))) == NULL){ 150 | diag("Couldn't allocate device (%s?)\n", strerror(errno)); 151 | zpool_close(zhp); 152 | return -1; 153 | } 154 | memset(d, 0, sizeof(*d)); 155 | strcpy(d->name, name); 156 | d->model = strdup("LLNL ZoL"); 157 | d->uuid = strdup(guid); 158 | d->layout = LAYOUT_ZPOOL; 159 | d->swapprio = SWAP_INVALID; 160 | d->size = dehumanize(size); 161 | if((d->physsec = (1u << dehumanize(ashift))) < 512u){ 162 | d->physsec = 512u; 163 | } 164 | d->logsec = 512; 165 | d->zpool.state = state; 166 | d->zpool.transport = AGGREGATE_UNKNOWN; 167 | d->zpool.zpoolver = version; 168 | add_new_virtual_blockdev(d); 169 | zpool_close(zhp); 170 | return 0; 171 | } 172 | 173 | static int 174 | zfscb(zfs_handle_t *zhf, void *arg){ 175 | char sbuf[BUFSIZ], *mnttype, *label; 176 | struct zpoolcb_t *cb = arg; 177 | const glightui *gui; 178 | uintmax_t totalsize; 179 | const char *zname; 180 | zfs_type_t ztype; 181 | int version; 182 | device *d; 183 | 184 | gui = cb->gui; 185 | zname = zfs_get_name(zhf); 186 | ztype = zfs_get_type(zhf); 187 | if(!zname || !ztype){ 188 | diag("Couldn't get ZFS name/type\n"); 189 | return -1; 190 | } 191 | lock_growlight(); 192 | if((d = lookup_device(zname)) == NULL){ 193 | unlock_growlight(); 194 | diag("Couldn't look up zpool named %s\n", zname); 195 | return -1; 196 | } 197 | unlock_growlight(); 198 | if((version = zfs_prop_get_int(zhf, ZFS_PROP_VERSION)) < 0){ 199 | diag("Couldn't get dataset version for %s\n", zname); 200 | return -1; 201 | } 202 | totalsize = 0; 203 | if(zfs_prop_get(zhf, ZFS_PROP_AVAILABLE, sbuf, sizeof(sbuf), NULL, NULL, 0, 0)){ 204 | diag("Couldn't get available size for %s\n", zname); 205 | return -1; 206 | } 207 | totalsize += dehumanize(sbuf); 208 | if(zfs_prop_get(zhf, ZFS_PROP_USED, sbuf, sizeof(sbuf), NULL, NULL, 0, 0)){ 209 | diag("Couldn't get used size for %s\n", zname); 210 | return 0; 211 | } 212 | totalsize += dehumanize(sbuf); 213 | // ZFS_PROP_MOUNTPOINT is the default mount point, not the current 214 | // mount point (which indeed might not exist). No need to look it up. 215 | // FIXME check for existing mnttype? 216 | if((mnttype = strdup("zfs")) == NULL || (label = strdup(zname)) == NULL){ 217 | diag("Couldn't dup string\n"); 218 | free(mnttype); 219 | return -1; 220 | } 221 | free(d->label); 222 | free(d->mnttype); 223 | d->label = label; 224 | d->mnttype = mnttype; 225 | d->mntsize = totalsize; 226 | if(d->layout == LAYOUT_PARTITION){ 227 | d = d->partdev.parent; 228 | } 229 | d->uistate = gui->block_event(d, d->uistate); 230 | return 0; 231 | } 232 | 233 | int scan_zpools(const glightui *gui){ 234 | libzfs_handle_t *zfs = zht; 235 | zprop_list_t *pools = NULL; 236 | struct zpoolcb_t cb; 237 | 238 | if(zht == NULL){ 239 | return 0; // ZFS wasn't successfully initialized 240 | } 241 | if(zprop_get_list(zfs, props, &pools, ZFS_TYPE_POOL)){ 242 | diag("Couldn't list ZFS pools\n"); 243 | return -1; 244 | } 245 | // FIXME do what with it? 246 | zprop_free_list(pools); 247 | memset(&cb, 0, sizeof(cb)); 248 | cb.gui = gui; 249 | if(zpool_iter(zht, zpoolcb, &cb)){ 250 | diag("Couldn't iterate over zpools\n"); 251 | return -1; 252 | } 253 | if(zfs_iter_root(zht, zfscb, &cb)){ 254 | diag("Couldn't iterate over root datasets\n"); 255 | return -1; 256 | } 257 | verbf("Found %u ZFS zpool%s\n", cb.pools, cb.pools == 1 ? "" : "s"); 258 | return 0; 259 | } 260 | 261 | int init_zfs_support(const glightui *gui){ 262 | if((zht = libzfs_init()) == NULL){ 263 | diag("Warning: couldn't initialize ZFS\n"); 264 | return 0; 265 | } 266 | libzfs_print_on_error(zht, verbose ? true : false); 267 | if(scan_zpools(gui)){ 268 | stop_zfs_support(); 269 | return -1; 270 | } 271 | verbf("Initialized ZFS support.\n"); 272 | return 0; 273 | } 274 | 275 | int stop_zfs_support(void){ 276 | if(zht){ 277 | libzfs_fini(zht); 278 | zht = NULL; 279 | } 280 | return 0; 281 | } 282 | 283 | int print_zfs_version(FILE *fp){ 284 | return fprintf(fp, "LLNL ZoL: ZPL version %s, SPA version %s\n", 285 | ZPL_VERSION_STRING, SPA_VERSION_STRING); 286 | } 287 | 288 | int destroy_zpool(device *d){ 289 | zpool_handle_t *zhp; 290 | 291 | if(d == NULL){ 292 | diag("Passed a NULL zpool\n"); 293 | return -1; 294 | } 295 | if(d->layout != LAYOUT_ZPOOL){ 296 | diag("%s is not a zpool\n", d->name); 297 | return -1; 298 | } 299 | if((zhp = zpool_open_canfail(zht, d->name)) == NULL){ 300 | diag("Couldn't open zpool %s\n", d->name); 301 | return -1; 302 | } 303 | // FIXME only pass force flag when necessary 304 | if(zpool_disable_datasets(zhp, 1)){ 305 | diag("Couldn't disable datasets on %s\n", d->name); 306 | zpool_close(zhp); 307 | return -1; 308 | } 309 | if(zpool_destroy(zhp, "destroyed by growlight")){ 310 | diag("Couldn't destroy %s\n", d->name); 311 | zpool_close(zhp); 312 | } 313 | zpool_close(zhp); 314 | return 0; 315 | } 316 | #else 317 | int init_zfs_support(const glightui *gui __attribute__ ((unused))){ 318 | diag("No ZFS support in this build.\n"); 319 | return 0; 320 | } 321 | 322 | int stop_zfs_support(void){ 323 | return 0; 324 | } 325 | 326 | int print_zfs_version(FILE *fp){ 327 | return fprintf(fp, "No ZFS support in this build.\n"); 328 | } 329 | 330 | int scan_zpools(const glightui *gui __attribute__ ((unused))){ 331 | verbf("No ZFS support in this build.\n"); 332 | return 0; 333 | } 334 | 335 | int destroy_zpool(device *d __attribute__ ((unused))){ 336 | diag("No ZFS support in this build.\n"); 337 | return 0; 338 | } 339 | #endif 340 | static int 341 | generic_make_zpool(const char *type, const char *name, char * const *vdevs, int num){ 342 | char buf[BUFSIZ]; 343 | size_t pos; 344 | int z; 345 | 346 | pos = 0; 347 | #define PREFIX "/dev/" 348 | for(z = 0 ; z < num ; ++z){ 349 | if((unsigned)snprintf(buf + pos, sizeof(buf) - pos, " %s%s", PREFIX, *vdevs) >= sizeof(buf) - pos){ 350 | diag("Too many arguments for zpool creation\n"); 351 | return -1; 352 | } 353 | ++vdevs; 354 | pos += strlen(buf + pos); 355 | } 356 | #undef PREFIX 357 | // FIXME also, ashift with 512-byte sectors wastes space 358 | // FIXME see notes below (make_zfs()) regarding unsafe use of -f 359 | return vspopen_drain("zpool create -f -oashift=12 %s %s %s", name, type, buf); 360 | } 361 | 362 | int make_zmirror(const char *name, char * const *vdevs, int num){ 363 | return generic_make_zpool("mirror", name, vdevs, num); 364 | } 365 | 366 | int make_raidz1(const char *name, char * const *vdevs, int num){ 367 | return generic_make_zpool("raidz1", name, vdevs, num); 368 | } 369 | 370 | int make_raidz2(const char *name, char * const *vdevs, int num){ 371 | return generic_make_zpool("raidz2", name, vdevs, num); 372 | } 373 | 374 | int make_raidz3(const char *name, char * const *vdevs, int num){ 375 | return generic_make_zpool("raidz3", name, vdevs, num); 376 | } 377 | 378 | // FIXME rather than using -f with creation, we ought make the vdevs conform to 379 | // the zpool create requirements: 380 | // 381 | // for entire devices: 382 | // - blank out mbr 383 | // - no partition table 384 | // - no filesystem signature 385 | int make_zfs(const char *dev, const struct mkfsmarshal *mkm){ 386 | return vspopen_drain("zpool create -f %s %s", mkm->name, dev); 387 | } 388 | 389 | // Remount a zfs 390 | int mount_zfs(device *d, const char *targ, unsigned mntops, const void *data){ 391 | if(mntops || data){ 392 | diag("Invalid arguments to zfs mount: %u %p\n", mntops, data); 393 | return -1; 394 | } 395 | vspopen_drain("zfs unmount %s", d->name); // FIXME 396 | if(vspopen_drain("zfs set mountpoint=%s %s", targ, d->name)){ 397 | vspopen_drain("zfs mount %s", d->name); 398 | return -1; 399 | } 400 | return vspopen_drain("zfs mount %s", d->name); 401 | } 402 | -------------------------------------------------------------------------------- /src/zfs.h: -------------------------------------------------------------------------------- 1 | // copyright 2012–2021 nick black 2 | #ifndef GROWLIGHT_ZFS 3 | #define GROWLIGHT_ZFS 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | struct device; 10 | 11 | #include "fs.h" 12 | #include 13 | #include "growlight.h" 14 | 15 | int init_zfs_support(const glightui *); 16 | int stop_zfs_support(void); 17 | int scan_zpools(const glightui *); 18 | int print_zfs_version(FILE *); 19 | int destroy_zpool(struct device *); 20 | 21 | int make_zmirror(const char *,char * const *,int); 22 | int make_raidz1(const char *,char * const *,int); 23 | int make_raidz2(const char *,char * const *,int); 24 | int make_raidz3(const char *,char * const *,int); 25 | 26 | // Make a zpool from a single device (not recommended) 27 | int make_zfs(const char *,const struct mkfsmarshal *); 28 | 29 | // Remount a zfs 30 | int mount_zfs(device *,const char *,unsigned,const void *); 31 | 32 | #ifdef __cplusplus 33 | } 34 | #endif 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /tests/gpt.cpp: -------------------------------------------------------------------------------- 1 | #include "main.h" 2 | #include "gpt.h" 3 | #include 4 | #include 5 | #include 6 | 7 | #define UUID "\x5E\x86\x90\xEF\xD0\x30\x03\x46\x99\x3D\x54\x6E\xB0\xE7\x1B\x0D" 8 | 9 | TEST_CASE("GPT") { 10 | 11 | // First eight bytes must be "EFI PART" 12 | SUBCASE("Signature") { 13 | gpt_header head; 14 | CHECK(0 == initialize_gpt(&head, 512, 4194287, 34, nullptr)); 15 | CHECK(0 == memcmp(&head.signature, "EFI PART", sizeof(head.signature))); 16 | } 17 | 18 | // Bytes 0x8--0xb must be 00 00 01 00 (1.00 by UEFI logic) 19 | SUBCASE("Revision") { 20 | gpt_header head; 21 | CHECK(0 == initialize_gpt(&head, 512, 4194287, 34, nullptr)); 22 | auto revision = head.revision; 23 | CHECK(0x00010000 == revision); 24 | } 25 | 26 | // Bytes 0xc--0xf must be >= 92, should be the logical block size 27 | SUBCASE("HeaderSize") { 28 | gpt_header head; 29 | CHECK(0 == initialize_gpt(&head, 512, 4194287, 34, nullptr)); 30 | auto headsize = head.headsize; 31 | CHECK(92 == headsize); 32 | } 33 | 34 | // Bytes 0x18--0x1f are the sector of the GPT primary, usually 1 35 | // Bytes 0x20--0x27 are the sector of the GPT alternate, provided as argument 36 | SUBCASE("GPTLBAs") { 37 | gpt_header head; 38 | CHECK(0 == initialize_gpt(&head, 512, 100000, 34, nullptr)); 39 | auto lba = head.lba; 40 | CHECK(1 == lba); 41 | auto backuplba = head.backuplba; 42 | CHECK(100000 == backuplba); 43 | } 44 | 45 | // Verify the 16-byte UUID is as specified 46 | SUBCASE("UUID") { 47 | gpt_header head; 48 | CHECK(0 == initialize_gpt(&head, 512, 4194287, 34, UUID)); 49 | CHECK(0 == memcmp(head.disk_guid, UUID, sizeof(head.disk_guid))); 50 | } 51 | 52 | SUBCASE("CRC32Sanity") { 53 | const unsigned char sample[] = "123456789"; 54 | uint32_t crc = crc32(0L, Z_NULL, 0); 55 | crc = crc32(crc, sample, sizeof(sample) - 1); 56 | CHECK(crc == 0xCBF43926); 57 | crc = crc32(0L, Z_NULL, 0); 58 | uint32_t db = htonl(0xdeadbeef); 59 | crc = crc32(crc, (const unsigned char*)&db, 4); 60 | CHECK(crc == 2090640218); 61 | } 62 | 63 | // Verify both CRCs, and the reserved area following HeaderCRC32 64 | SUBCASE("CRC32") { 65 | gpt_header head; 66 | CHECK(0 == initialize_gpt(&head, 512, 4194287, 34, UUID)); 67 | // partition entry size must be a positive multiple of 128 (usually 128) 68 | auto partsize = head.partsize; 69 | CHECK(0 < partsize); 70 | CHECK(0 == (partsize % 128)); 71 | // number of partition entries, usually 128 (MINIMUM_GPT_ENTRIES) 72 | auto partcount = head.partcount; 73 | CHECK(128 <= partcount); 74 | auto entries = new gpt_entry[partcount]; 75 | memset(entries, 0, sizeof(*entries) * partcount); 76 | CHECK(0 == update_crc(&head, entries)); 77 | auto crc = head.crc; 78 | // FIXME fix on big-endian! 79 | WARN(2006165414 == crc); 80 | auto reserved = head.reserved; 81 | CHECK(0 == reserved); 82 | auto partcrc = head.partcrc; 83 | CHECK(2874462854 == partcrc); 84 | delete[] entries; 85 | } 86 | 87 | // Check that LBA of 512 sets header size and zeroes out remainder of sector. 88 | // The Unified Extensible Firmware Interface Specification, Version 2.3.1, 89 | // Errata C, June 27, 2012, states on page 104, in Table 16: "Size in bytes 90 | // of the GPT Header. The HeaderSize must be greater than 92 and must be less 91 | // than or equal to the logical block size." But everyone uses 92 :/. 92 | SUBCASE("FullLBA") { 93 | unsigned char sector[512]; 94 | memset(sector, 0xff, sizeof(sector)); 95 | gpt_header* head = reinterpret_cast(sector); 96 | CHECK(0 == initialize_gpt(head, sizeof(sector), 4194287, 34, nullptr)); 97 | auto headsize = head->headsize; 98 | CHECK(92 == headsize); 99 | for(size_t idx = 92 ; idx < sizeof(sector) ; ++idx){ 100 | CHECK(0 == sector[idx]); 101 | } 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT 2 | #include "main.h" 3 | 4 | auto main(int argc, const char **argv) -> int { 5 | if(!setlocale(LC_ALL, "")){ 6 | std::cerr << "Couldn't set locale based on user preferences!" << std::endl; 7 | return EXIT_FAILURE; 8 | } 9 | const char* lang = getenv("LANG"); 10 | if(lang == nullptr){ 11 | std::cerr << "Warning: LANG wasn't defined" << std::endl; 12 | }else{ 13 | std::cout << "Running with LANG=" << lang << std::endl; 14 | } 15 | const char* term = getenv("TERM"); 16 | // ubuntu's buildd sets TERM=unknown, fuck it, handle this atrocity 17 | if(term == nullptr || strcmp(term, "unknown") == 0){ 18 | std::cerr << "TERM wasn't defined, exiting with success" << std::endl; 19 | return EXIT_SUCCESS; 20 | } 21 | std::cout << "Running with TERM=" << term << std::endl; 22 | doctest::Context context; 23 | 24 | context.setOption("order-by", "name"); // sort the test cases by their name 25 | 26 | context.applyCommandLine(argc, argv); 27 | 28 | // overrides 29 | context.setOption("no-breaks", true); // don't break in the debugger when assertions fail 30 | 31 | int res = context.run(); // run 32 | 33 | if(context.shouldExit()){ // important - query flags (and --exit) rely on the user doing this 34 | return res; // propagate the result of the tests 35 | } 36 | 37 | return res; // the result from doctest is propagated here as well 38 | } 39 | -------------------------------------------------------------------------------- /tests/main.h: -------------------------------------------------------------------------------- 1 | #ifndef GROWLIGHT_TESTS_MAIN 2 | #define GROWLIGHT_TESTS_MAIN 3 | 4 | #include 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /tools/debrelease: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | usage() { echo "usage: `basename $0` version" ; } 6 | 7 | [ $# -eq 1 ] || { usage >&2 ; exit 1 ; } 8 | 9 | VERSION="$1" 10 | 11 | dch -v $VERSION-1 12 | dch -r 13 | uscan --force --repack --compression xz 14 | git commit -m "v$VERSION" -a 15 | gbp import-orig ../growlight_$VERSION.orig.tar.xz 16 | git push --tags 17 | dpkg-buildpackage --build=source 18 | cd .. 19 | sudo pbuilder build growlight*dsc 20 | cd - 21 | git push 22 | rm debian/files 23 | -------------------------------------------------------------------------------- /tools/release: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | usage() { echo "usage: `basename $0` oldversion newversion" ; } 6 | 7 | [ $# -eq 2 ] || { usage >&2 ; exit 1 ; } 8 | 9 | OLDVERSION="$1" 10 | VERSION="$2" 11 | 12 | git clean -f -d -x 13 | 14 | # bump version numbers wherever they occur (wherever we enumerate them, anyway) 15 | sed -i -e "s/\(project(growlight VERSION \)$OLDVERSION/\1$VERSION/" CMakeLists.txt 16 | for i in doc/man/man*/*.md ; do 17 | sed -i -e "s/% v$OLDVERSION/% v$VERSION/" "$i" 18 | done 19 | 20 | BUILDDIR="build-$VERSION" 21 | mkdir "$BUILDDIR" 22 | cd "$BUILDDIR" 23 | cmake .. 24 | make -j 25 | 26 | # if that all worked, commit, push, and tag 27 | git commit -a -m v$VERSION 28 | git push 29 | git pull 30 | git tag -a v$VERSION -m v$VERSION -s 31 | git push origin --tags 32 | git pull 33 | TARBALL=v$VERSION.tar.gz 34 | wget https://github.com/dankamongmen/growlight/archive/$TARBALL 35 | gpg --sign --armor --detach-sign $TARBALL 36 | rm v$VERSION.tar.gz 37 | 38 | echo "Cut $VERSION, signed to $TARBALL.asc" 39 | echo "Now uploadling the sig to https://github.com/dankamongmen/growlight/releases" 40 | echo "The bastards are trying to immanentize the Eschaton" 41 | 42 | # requires token in ~/.netrc 43 | github-release dankamongmen/growlight create v$VERSION --name "v$VERSION" --publish $TARBALL.asc 44 | rm $TARBALL.asc 45 | 46 | # build and upload documentation package 47 | cd "$BUILDDIR" 48 | tar czvf growlight-doc-$VERSION.tar.gz *.1 *.3 *.html 49 | github-asset dankamongmen/growlight upload v$VERSION growlight-doc-$VERSION.tar.gz 50 | --------------------------------------------------------------------------------