├── .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 | [](https://drone.dsscaw.com:4443/dankamongmen/growlight)
12 |
13 |
14 |
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 |
--------------------------------------------------------------------------------