ohmu
·
2026-10-15
1#include "btrfs.h"
2
3#include "common/debug.h"
4#include "common/io.h"
5#include <fcntl.h>
6
7enum { uuidLen = (uint32_t) __builtin_strlen("00000000-0000-0000-0000-000000000000") };
8
9static const char* enumerateDevices(FFBtrfsResult* item, int dfd, FFstrbuf* buffer) {
10 int subfd = openat(dfd, "devices", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
11 if (subfd < 0) {
12 FF_DEBUG("openat(\"devices\") failed: %s", strerror(errno));
13 return "openat(\"/sys/fs/btrfs/UUID/devices\") == -1";
14 }
15
16 FF_AUTO_CLOSE_DIR DIR* dirp = fdopendir(subfd);
17 if (dirp == nullptr) {
18 FF_DEBUG("fdopendir(\"devices\") failed: %s", strerror(errno));
19 close(subfd);
20 return "fdopendir(\"/sys/fs/btrfs/UUID/devices\") == nullptr";
21 }
22
23 struct dirent* entry;
24 while ((entry = readdir(dirp)) != nullptr) {
25 if (entry->d_name[0] == '.') {
26 continue;
27 }
28
29 if (item->devices.length) {
30 ffStrbufAppendC(&item->devices, ',');
31 }
32 ffStrbufAppendS(&item->devices, entry->d_name);
33
34 char path[sizeof(entry->d_name) + sizeof("/size") + 1];
35 snprintf(path, ARRAY_SIZE(path), "%s/size", entry->d_name);
36
37 if (ffReadFileBufferRelative(subfd, path, buffer)) {
38 item->totalSize += ffStrbufToUInt(buffer, 0) * 512;
39 }
40 }
41
42 return nullptr;
43}
44
45static const char* enumerateFeatures(FFBtrfsResult* item, int dfd) {
46 int subfd = openat(dfd, "features", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
47 if (subfd < 0) {
48 FF_DEBUG("openat(\"features\") failed: %s", strerror(errno));
49 return "openat(\"/sys/fs/btrfs/UUID/features\") == -1";
50 }
51
52 FF_AUTO_CLOSE_DIR DIR* dirp = fdopendir(subfd);
53 if (dirp == nullptr) {
54 FF_DEBUG("fdopendir(\"features\") failed: %s", strerror(errno));
55 return "fdopendir(\"/sys/fs/btrfs/UUID/features\") == nullptr";
56 }
57
58 struct dirent* entry;
59 while ((entry = readdir(dirp)) != nullptr) {
60 if (entry->d_name[0] == '.') {
61 continue;
62 }
63 if (item->features.length) {
64 ffStrbufAppendC(&item->features, ',');
65 }
66 ffStrbufAppendS(&item->features, entry->d_name);
67 }
68
69 return nullptr;
70}
71
72static const char* detectAllocation(FFBtrfsResult* item, int dfd, FFstrbuf* buffer) {
73 FF_AUTO_CLOSE_FD int subfd = openat(dfd, "allocation", O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);
74 if (subfd < 0) {
75 FF_DEBUG("openat(\"allocation\") failed: %s", strerror(errno));
76 return "openat(\"/sys/fs/btrfs/UUID/allocation\") == -1";
77 }
78
79 if (ffReadFileBufferRelative(subfd, "global_rsv_size", buffer)) {
80 item->globalReservationTotal = ffStrbufToUInt(buffer, 0);
81 } else {
82 FF_DEBUG("ffReadFileBuffer(\"global_rsv_size\") failed");
83 return "ffReadFileBuffer(\"/sys/fs/btrfs/UUID/allocation/global_rsv_size\") == nullptr";
84 }
85
86 if (ffReadFileBufferRelative(subfd, "global_rsv_reserved", buffer)) {
87 // global_rsv_reserved is already the used part of the reservation, global_rsv_size its total
88 item->globalReservationUsed = ffStrbufToUInt(buffer, 0);
89 }
90
91#define FF_BTRFS_DETECT_PROFILE(_index, _type, _profile, _copies) \
92 else if (faccessat(subfd, _type "/" _profile "/", F_OK, 0) == 0) { \
93 item->allocation[_index].profile = _profile; \
94 item->allocation[_index].copies = _copies; \
95 }
96
97#define FF_BTRFS_DETECT_TYPE(_index, _type) \
98 do { \
99 item->allocation[_index].type = _type; \
100 if (ffReadFileBufferRelative(subfd, _type "/total_bytes", buffer)) \
101 item->allocation[_index].total = ffStrbufToUInt(buffer, 0); \
102 \
103 if (ffReadFileBufferRelative(subfd, _type "/bytes_used", buffer)) \
104 item->allocation[_index].used = ffStrbufToUInt(buffer, 0); \
105 \
106 if (false) {} \
107 FF_BTRFS_DETECT_PROFILE(_index, _type, "single", 1) \
108 FF_BTRFS_DETECT_PROFILE(_index, _type, "dup", 2) \
109 FF_BTRFS_DETECT_PROFILE(_index, _type, "raid0", 1) \
110 FF_BTRFS_DETECT_PROFILE(_index, _type, "raid1", 2) \
111 FF_BTRFS_DETECT_PROFILE(_index, _type, "raid10", 2) \
112 FF_BTRFS_DETECT_PROFILE(_index, _type, "raid1c3", 3) \
113 FF_BTRFS_DETECT_PROFILE(_index, _type, "raid1c4", 4) \
114 FF_BTRFS_DETECT_PROFILE(_index, _type, "raid5", 1) /* (n-1)/n */ \
115 FF_BTRFS_DETECT_PROFILE(_index, _type, "raid6", 1) /* (n-2)/n */ \
116 else { \
117 item->allocation[_index].profile = "unknown"; \
118 item->allocation[_index].copies = 1; \
119 } \
120 } while (0)
121
122 FF_BTRFS_DETECT_TYPE(0, "data");
123 FF_BTRFS_DETECT_TYPE(1, "metadata");
124 FF_BTRFS_DETECT_TYPE(2, "system");
125
126#undef FF_BTRFS_DETECT_TYPE
127
128 return nullptr;
129}
130
131const char* ffDetectBtrfs(FFlist* result) {
132 FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/fs/btrfs/");
133 if (dirp == nullptr) {
134 FF_DEBUG("opendir(\"/sys/fs/btrfs\") failed: %s", strerror(errno));
135 return "opendir(\"/sys/fs/btrfs\") == nullptr";
136 }
137
138 FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
139
140 struct dirent* entry;
141 while ((entry = readdir(dirp)) != nullptr) {
142 if (entry->d_name[0] == '.') {
143 continue;
144 }
145 if (strlen(entry->d_name) != uuidLen) {
146 continue;
147 }
148
149 FFBtrfsResult* item = FF_LIST_ADD(FFBtrfsResult, *result);
150 (*item) = (FFBtrfsResult) {
151 .uuid = ffStrbufCreateNS(uuidLen, entry->d_name),
152 .name = ffStrbufCreate(),
153 .devices = ffStrbufCreate(),
154 .features = ffStrbufCreate(),
155 };
156
157 FF_AUTO_CLOSE_FD int dfd = openat(dirfd(dirp), entry->d_name, O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);
158 if (dfd < 0) {
159 continue;
160 }
161
162 if (ffAppendFileBufferRelative(dfd, "label", &item->name)) {
163 ffStrbufTrimRightSpace(&item->name);
164 }
165
166 enumerateDevices(item, dfd, &buffer);
167
168 enumerateFeatures(item, dfd);
169
170 if (ffReadFileBufferRelative(dfd, "generation", &buffer)) {
171 item->generation = (uint32_t) ffStrbufToUInt(&buffer, 0);
172 }
173
174 if (ffReadFileBufferRelative(dfd, "nodesize", &buffer)) {
175 item->nodeSize = (uint32_t) ffStrbufToUInt(&buffer, 0);
176 }
177
178 if (ffReadFileBufferRelative(dfd, "sectorsize", &buffer)) {
179 item->sectorSize = (uint32_t) ffStrbufToUInt(&buffer, 0);
180 }
181
182 detectAllocation(item, dfd, &buffer);
183 }
184
185 return nullptr;
186}