]>
Commit | Line | Data |
---|---|---|
959ef981 | 1 | // SPDX-License-Identifier: GPL-2.0 |
2bd0ea18 | 2 | /* |
da23017d NS |
3 | * Copyright (c) 2000-2005 Silicon Graphics, Inc. |
4 | * All Rights Reserved. | |
2bd0ea18 NS |
5 | */ |
6 | ||
dcabd4e7 | 7 | #include "platform_defs.h" |
6b803e5a | 8 | #include "command.h" |
fb2bfcdf | 9 | #include "input.h" |
e246ba5f | 10 | #include "init.h" |
48c46ee3 | 11 | #include "io.h" |
fee68490 | 12 | #include "libfrog/fsgeom.h" |
2bd0ea18 | 13 | |
e246ba5f | 14 | static cmdinfo_t bmap_cmd; |
2bd0ea18 | 15 | |
e246ba5f NS |
16 | static void |
17 | bmap_help(void) | |
2bd0ea18 | 18 | { |
e246ba5f NS |
19 | printf(_( |
20 | "\n" | |
21 | " prints the block mapping for an XFS file's data or attribute forks" | |
22 | "\n" | |
23 | " Example:\n" | |
24 | " 'bmap -vp' - tabular format verbose map, including unwritten extents\n" | |
25 | "\n" | |
26 | " bmap prints the map of disk blocks used by the current file.\n" | |
27 | " The map lists each extent used by the file, as well as regions in the\n" | |
28 | " file that do not have any corresponding blocks (holes).\n" | |
29 | " By default, each line of the listing takes the following form:\n" | |
30 | " extent: [startoffset..endoffset]: startblock..endblock\n" | |
31 | " Holes are marked by replacing the startblock..endblock with 'hole'.\n" | |
32 | " All the file offsets and disk blocks are in units of 512-byte blocks.\n" | |
33 | " -a -- prints the attribute fork map instead of the data fork.\n" | |
7536ce44 | 34 | " -c -- prints the copy-on-write fork map instead of the data fork.\n" |
c9a048f2 | 35 | " This works only if the kernel was compiled in debug mode.\n" |
7536ce44 | 36 | " -e -- print delayed allocation extents.\n" |
e246ba5f | 37 | " -l -- also displays the length of each extent in 512-byte blocks.\n" |
6f0d352c ES |
38 | " -n -- query n extents.\n" |
39 | " -p -- obtain all unwritten extents as well (w/ -v show which are unwritten.)\n" | |
40 | " -v -- Verbose information, specify ag info. Show flags legend on 2nd -v\n" | |
e246ba5f NS |
41 | " Note: the bmap for non-regular files can be obtained provided the file\n" |
42 | " was opened appropriately (in particular, must be opened read-only).\n" | |
43 | "\n")); | |
44 | } | |
2bd0ea18 | 45 | |
00ff2b10 | 46 | static int |
e246ba5f NS |
47 | bmap_f( |
48 | int argc, | |
49 | char **argv) | |
50 | { | |
51 | struct fsxattr fsx; | |
52 | struct getbmapx *map; | |
c0211f67 | 53 | struct xfs_fsop_geom fsgeo; |
e246ba5f NS |
54 | int map_size; |
55 | int loop = 0; | |
56 | int flg = 0; | |
57 | int aflag = 0; | |
7536ce44 | 58 | int cflag = 0; |
e246ba5f NS |
59 | int lflag = 0; |
60 | int nflag = 0; | |
66b57340 | 61 | int pflag = 0; |
e246ba5f | 62 | int vflag = 0; |
e2eb7f3a | 63 | int is_rt = 0; |
e246ba5f NS |
64 | int bmv_iflags = 0; /* flags for XFS_IOC_GETBMAPX */ |
65 | int i = 0; | |
66 | int c; | |
619df821 | 67 | int egcnt; |
e246ba5f | 68 | |
4cfe2c37 | 69 | while ((c = getopt(argc, argv, "aceln:pv")) != EOF) { |
e246ba5f NS |
70 | switch (c) { |
71 | case 'a': /* Attribute fork. */ | |
2bd0ea18 NS |
72 | bmv_iflags |= BMV_IF_ATTRFORK; |
73 | aflag = 1; | |
74 | break; | |
7536ce44 DW |
75 | case 'c': /* CoW fork. */ |
76 | bmv_iflags |= BMV_IF_COWFORK | BMV_IF_DELALLOC; | |
77 | cflag = 1; | |
78 | break; | |
79 | case 'e': | |
80 | bmv_iflags |= BMV_IF_DELALLOC; | |
81 | break; | |
e246ba5f | 82 | case 'l': /* list number of blocks with each extent */ |
2bd0ea18 NS |
83 | lflag = 1; |
84 | break; | |
e246ba5f | 85 | case 'n': /* number of extents specified */ |
2bd0ea18 NS |
86 | nflag = atoi(optarg); |
87 | break; | |
2bd0ea18 NS |
88 | case 'p': |
89 | /* report unwritten preallocated blocks */ | |
66b57340 | 90 | pflag = 1; |
2bd0ea18 NS |
91 | bmv_iflags |= BMV_IF_PREALLOC; |
92 | break; | |
e246ba5f | 93 | case 'v': /* Verbose output */ |
2bd0ea18 NS |
94 | vflag++; |
95 | break; | |
2bd0ea18 | 96 | default: |
48c46ee3 | 97 | return command_usage(&bmap_cmd); |
2bd0ea18 NS |
98 | } |
99 | } | |
7536ce44 | 100 | if (aflag || cflag) |
4cfe2c37 | 101 | bmv_iflags &= ~BMV_IF_PREALLOC; |
2bd0ea18 NS |
102 | |
103 | if (vflag) { | |
03d96c64 | 104 | c = -xfrog_geometry(file->fd, &fsgeo); |
9612817d | 105 | if (c) { |
9440d84d NS |
106 | fprintf(stderr, |
107 | _("%s: can't get geometry [\"%s\"]: %s\n"), | |
9612817d | 108 | progname, file->name, strerror(c)); |
e246ba5f NS |
109 | exitcode = 1; |
110 | return 0; | |
2bd0ea18 | 111 | } |
83f4b5ac | 112 | c = xfsctl(file->name, file->fd, FS_IOC_FSGETXATTR, &fsx); |
48c46ee3 | 113 | if (c < 0) { |
9440d84d NS |
114 | fprintf(stderr, |
115 | _("%s: cannot read attrs on \"%s\": %s\n"), | |
48c46ee3 | 116 | progname, file->name, strerror(errno)); |
e246ba5f NS |
117 | exitcode = 1; |
118 | return 0; | |
2bd0ea18 NS |
119 | } |
120 | ||
b1faed5f | 121 | if (fsx.fsx_xflags & FS_XFLAG_REALTIME) { |
dfc130f3 | 122 | /* |
2bd0ea18 NS |
123 | * ag info not applicable to rt, continue |
124 | * without ag output. | |
125 | */ | |
e2eb7f3a | 126 | is_rt = 1; |
2bd0ea18 NS |
127 | } |
128 | } | |
129 | ||
0b52c691 | 130 | map_size = nflag ? nflag+2 : 32; /* initial guess - 32 */ |
2bd0ea18 NS |
131 | map = malloc(map_size*sizeof(*map)); |
132 | if (map == NULL) { | |
9440d84d | 133 | fprintf(stderr, _("%s: malloc of %d bytes failed.\n"), |
c03d02f8 | 134 | progname, (int)(map_size * sizeof(*map))); |
e246ba5f NS |
135 | exitcode = 1; |
136 | return 0; | |
2bd0ea18 | 137 | } |
dfc130f3 | 138 | |
2bd0ea18 | 139 | |
93d9f139 | 140 | /* Try the xfsctl(XFS_IOC_GETBMAPX) for the number of extents specified |
0b52c691 | 141 | * by nflag, or the initial guess number of extents (32). |
2bd0ea18 | 142 | * |
93d9f139 | 143 | * If there are more extents than we guessed, use xfsctl |
83f4b5ac | 144 | * (FS_IOC_FSGETXATTR[A]) to get the extent count, realloc some more |
2bd0ea18 NS |
145 | * space based on this count, and try again. |
146 | * | |
147 | * If the initial FGETBMAPX attempt returns EINVAL, this may mean | |
148 | * that we tried the FGETBMAPX on a zero length file. If we get | |
149 | * EINVAL, check the length with fstat() and return "no extents" | |
150 | * if the length == 0. | |
151 | * | |
4cfe2c37 AI |
152 | * Why not do the xfsctl(FS_IOC_FSGETXATTR[A]) first? |
153 | * The extent count may be wrong for a file with delayed | |
154 | * allocation blocks. The XFS_IOC_GETBMAPX forces the real | |
155 | * allocation and fixes up the extent count. | |
2bd0ea18 NS |
156 | */ |
157 | ||
158 | do { /* loop a miximum of two times */ | |
159 | ||
dab9b8d6 | 160 | memset(map, 0, sizeof(*map)); /* zero header */ |
2bd0ea18 NS |
161 | |
162 | map->bmv_length = -1; | |
163 | map->bmv_count = map_size; | |
164 | map->bmv_iflags = bmv_iflags; | |
165 | ||
48c46ee3 | 166 | i = xfsctl(file->name, file->fd, XFS_IOC_GETBMAPX, map); |
2bd0ea18 NS |
167 | if (i < 0) { |
168 | if ( errno == EINVAL | |
e246ba5f | 169 | && !aflag && filesize() == 0) { |
2bd0ea18 NS |
170 | break; |
171 | } else { | |
93d9f139 NS |
172 | fprintf(stderr, _("%s: xfsctl(XFS_IOC_GETBMAPX)" |
173 | " iflags=0x%x [\"%s\"]: %s\n"), | |
48c46ee3 | 174 | progname, map->bmv_iflags, file->name, |
c03d02f8 | 175 | strerror(errno)); |
2bd0ea18 | 176 | free(map); |
e246ba5f NS |
177 | exitcode = 1; |
178 | return 0; | |
2bd0ea18 NS |
179 | } |
180 | } | |
181 | if (nflag) | |
182 | break; | |
183 | if (map->bmv_entries < map->bmv_count-1) | |
184 | break; | |
83f4b5ac | 185 | /* Get number of extents from xfsctl FS_IOC_FSGETXATTR[A] |
2bd0ea18 NS |
186 | * syscall. |
187 | */ | |
48c46ee3 | 188 | i = xfsctl(file->name, file->fd, aflag ? |
83f4b5ac | 189 | XFS_IOC_FSGETXATTRA : FS_IOC_FSGETXATTR, &fsx); |
2bd0ea18 | 190 | if (i < 0) { |
83f4b5ac | 191 | fprintf(stderr, "%s: xfsctl(FS_IOC_FSGETXATTR%s) " |
93d9f139 | 192 | "[\"%s\"]: %s\n", progname, aflag ? "A" : "", |
48c46ee3 | 193 | file->name, strerror(errno)); |
2bd0ea18 | 194 | free(map); |
e246ba5f NS |
195 | exitcode = 1; |
196 | return 0; | |
2bd0ea18 | 197 | } |
f6d1a563 TM |
198 | if (2 * fsx.fsx_nextents > map_size) { |
199 | map_size = 2 * fsx.fsx_nextents + 1; | |
2bd0ea18 NS |
200 | map = realloc(map, map_size*sizeof(*map)); |
201 | if (map == NULL) { | |
9440d84d NS |
202 | fprintf(stderr, |
203 | _("%s: cannot realloc %d bytes\n"), | |
c03d02f8 | 204 | progname, (int)(map_size*sizeof(*map))); |
e246ba5f NS |
205 | exitcode = 1; |
206 | return 0; | |
2bd0ea18 NS |
207 | } |
208 | } | |
209 | } while (++loop < 2); | |
210 | if (!nflag) { | |
211 | if (map->bmv_entries <= 0) { | |
48c46ee3 | 212 | printf(_("%s: no extents\n"), file->name); |
2bd0ea18 NS |
213 | free(map); |
214 | return 0; | |
215 | } | |
216 | } | |
619df821 | 217 | egcnt = nflag ? min(nflag, map->bmv_entries) : map->bmv_entries; |
48c46ee3 | 218 | printf("%s:\n", file->name); |
2bd0ea18 | 219 | if (!vflag) { |
619df821 | 220 | for (i = 0; i < egcnt; i++) { |
2bd0ea18 | 221 | printf("\t%d: [%lld..%lld]: ", i, |
5b64e00a | 222 | (long long) map[i + 1].bmv_offset, |
dfc130f3 | 223 | (long long)(map[i + 1].bmv_offset + |
5b64e00a | 224 | map[i + 1].bmv_length - 1LL)); |
2bd0ea18 | 225 | if (map[i + 1].bmv_block == -1) |
9440d84d | 226 | printf(_("hole")); |
1c47bd67 DW |
227 | else if (map[i + 1].bmv_block == -2) |
228 | printf(_("delalloc")); | |
2bd0ea18 | 229 | else { |
5b64e00a NS |
230 | printf("%lld..%lld", |
231 | (long long) map[i + 1].bmv_block, | |
232 | (long long)(map[i + 1].bmv_block + | |
233 | map[i + 1].bmv_length - 1LL)); | |
2bd0ea18 NS |
234 | |
235 | } | |
236 | if (lflag) | |
9440d84d NS |
237 | printf(_(" %lld blocks\n"), |
238 | (long long)map[i+1].bmv_length); | |
2bd0ea18 NS |
239 | else |
240 | printf("\n"); | |
241 | } | |
242 | } else { | |
243 | /* | |
dfc130f3 | 244 | * Verbose mode displays: |
2bd0ea18 | 245 | * extent: [startoffset..endoffset]: startblock..endblock \ |
dfc130f3 | 246 | * ag# (agoffset..agendoffset) totalbbs |
2bd0ea18 NS |
247 | */ |
248 | #define MINRANGE_WIDTH 16 | |
249 | #define MINAG_WIDTH 2 | |
250 | #define MINTOT_WIDTH 5 | |
7536ce44 DW |
251 | #define NFLG 6 /* count of flags */ |
252 | #define FLG_NULL 0000000 /* Null flag */ | |
253 | #define FLG_SHARED 0100000 /* shared extent */ | |
254 | #define FLG_PRE 0010000 /* Unwritten extent */ | |
255 | #define FLG_BSU 0001000 /* Not on begin of stripe unit */ | |
256 | #define FLG_ESU 0000100 /* Not on end of stripe unit */ | |
257 | #define FLG_BSW 0000010 /* Not on begin of stripe width */ | |
258 | #define FLG_ESW 0000001 /* Not on end of stripe width */ | |
4ca431fc NS |
259 | int agno; |
260 | off64_t agoff, bbperag; | |
dfc130f3 RC |
261 | int foff_w, boff_w, aoff_w, tot_w, agno_w; |
262 | char rbuf[32], bbuf[32], abuf[32]; | |
4ca431fc | 263 | int sunit, swidth; |
2bd0ea18 NS |
264 | |
265 | foff_w = boff_w = aoff_w = MINRANGE_WIDTH; | |
266 | tot_w = MINTOT_WIDTH; | |
e2eb7f3a BN |
267 | if (is_rt) |
268 | sunit = swidth = bbperag = 0; | |
269 | else { | |
270 | bbperag = (off64_t)fsgeo.agblocks * | |
271 | (off64_t)fsgeo.blocksize / BBSIZE; | |
272 | sunit = (fsgeo.sunit * fsgeo.blocksize) / BBSIZE; | |
273 | swidth = (fsgeo.swidth * fsgeo.blocksize) / BBSIZE; | |
274 | } | |
66b57340 | 275 | flg = sunit | pflag; |
2bd0ea18 | 276 | |
dfc130f3 | 277 | /* |
2bd0ea18 NS |
278 | * Go through the extents and figure out the width |
279 | * needed for all columns. | |
280 | */ | |
619df821 | 281 | for (i = 0; i < egcnt; i++) { |
dfc130f3 | 282 | snprintf(rbuf, sizeof(rbuf), "[%lld..%lld]:", |
5b64e00a NS |
283 | (long long) map[i + 1].bmv_offset, |
284 | (long long)(map[i + 1].bmv_offset + | |
285 | map[i + 1].bmv_length - 1LL)); | |
d371bee3 NS |
286 | if (map[i + 1].bmv_oflags & BMV_OF_PREALLOC) |
287 | flg = 1; | |
2bd0ea18 | 288 | if (map[i + 1].bmv_block == -1) { |
dfc130f3 RC |
289 | foff_w = max(foff_w, strlen(rbuf)); |
290 | tot_w = max(tot_w, | |
fb2bfcdf | 291 | numlen(map[i+1].bmv_length, 10)); |
2bd0ea18 | 292 | } else { |
dfc130f3 | 293 | snprintf(bbuf, sizeof(bbuf), "%lld..%lld", |
5b64e00a NS |
294 | (long long) map[i + 1].bmv_block, |
295 | (long long)(map[i + 1].bmv_block + | |
296 | map[i + 1].bmv_length - 1LL)); | |
dfc130f3 | 297 | boff_w = max(boff_w, strlen(bbuf)); |
e2eb7f3a BN |
298 | if (!is_rt) { |
299 | agno = map[i + 1].bmv_block / bbperag; | |
300 | agoff = map[i + 1].bmv_block - | |
301 | (agno * bbperag); | |
302 | snprintf(abuf, sizeof(abuf), | |
303 | "(%lld..%lld)", | |
304 | (long long)agoff, | |
305 | (long long)(agoff + | |
306 | map[i + 1].bmv_length - 1LL)); | |
307 | aoff_w = max(aoff_w, strlen(abuf)); | |
308 | } else | |
309 | aoff_w = 0; | |
310 | foff_w = max(foff_w, strlen(rbuf)); | |
dfc130f3 | 311 | tot_w = max(tot_w, |
fb2bfcdf | 312 | numlen(map[i+1].bmv_length, 10)); |
2bd0ea18 NS |
313 | } |
314 | } | |
fb2bfcdf | 315 | agno_w = is_rt ? 0 : max(MINAG_WIDTH, numlen(fsgeo.agcount, 10)); |
d371bee3 | 316 | printf("%4s: %-*s %-*s %*s %-*s %*s%s\n", |
9440d84d NS |
317 | _("EXT"), |
318 | foff_w, _("FILE-OFFSET"), | |
e2eb7f3a BN |
319 | boff_w, is_rt ? _("RT-BLOCK-RANGE") : _("BLOCK-RANGE"), |
320 | agno_w, is_rt ? "" : _("AG"), | |
321 | aoff_w, is_rt ? "" : _("AG-OFFSET"), | |
d371bee3 NS |
322 | tot_w, _("TOTAL"), |
323 | flg ? _(" FLAGS") : ""); | |
619df821 | 324 | for (i = 0; i < egcnt; i++) { |
d371bee3 NS |
325 | flg = FLG_NULL; |
326 | if (map[i + 1].bmv_oflags & BMV_OF_PREALLOC) { | |
327 | flg |= FLG_PRE; | |
328 | } | |
7536ce44 DW |
329 | if (map[i + 1].bmv_oflags & BMV_OF_SHARED) |
330 | flg |= FLG_SHARED; | |
331 | if (map[i + 1].bmv_oflags & BMV_OF_DELALLOC) | |
332 | map[i + 1].bmv_block = -2; | |
4595c940 JK |
333 | /* |
334 | * If striping enabled, determine if extent starts/ends | |
335 | * on a stripe unit boundary. | |
336 | */ | |
4ca431fc | 337 | if (sunit) { |
4ca431fc NS |
338 | if (map[i + 1].bmv_block % sunit != 0) { |
339 | flg |= FLG_BSU; | |
340 | } | |
dfc130f3 | 341 | if (((map[i + 1].bmv_block + |
4ca431fc NS |
342 | map[i + 1].bmv_length ) % sunit ) != 0) { |
343 | flg |= FLG_ESU; | |
344 | } | |
345 | if (map[i + 1].bmv_block % swidth != 0) { | |
346 | flg |= FLG_BSW; | |
347 | } | |
dfc130f3 | 348 | if (((map[i + 1].bmv_block + |
4ca431fc NS |
349 | map[i + 1].bmv_length ) % swidth ) != 0) { |
350 | flg |= FLG_ESW; | |
351 | } | |
352 | } | |
dfc130f3 | 353 | snprintf(rbuf, sizeof(rbuf), "[%lld..%lld]:", |
5b64e00a NS |
354 | (long long) map[i + 1].bmv_offset, |
355 | (long long)(map[i + 1].bmv_offset + | |
356 | map[i + 1].bmv_length - 1LL)); | |
2bd0ea18 | 357 | if (map[i + 1].bmv_block == -1) { |
dfc130f3 RC |
358 | printf("%4d: %-*s %-*s %*s %-*s %*lld\n", |
359 | i, | |
360 | foff_w, rbuf, | |
9440d84d | 361 | boff_w, _("hole"), |
2bd0ea18 | 362 | agno_w, "", |
dfc130f3 | 363 | aoff_w, "", |
5b64e00a | 364 | tot_w, (long long)map[i+1].bmv_length); |
7536ce44 DW |
365 | } else if (map[i + 1].bmv_block == -2) { |
366 | printf("%4d: %-*s %-*s %*s %-*s %*lld\n", | |
367 | i, | |
368 | foff_w, rbuf, | |
369 | boff_w, _("delalloc"), | |
370 | agno_w, "", | |
371 | aoff_w, "", | |
372 | tot_w, (long long)map[i+1].bmv_length); | |
2bd0ea18 | 373 | } else { |
dfc130f3 | 374 | snprintf(bbuf, sizeof(bbuf), "%lld..%lld", |
5b64e00a NS |
375 | (long long) map[i + 1].bmv_block, |
376 | (long long)(map[i + 1].bmv_block + | |
377 | map[i + 1].bmv_length - 1LL)); | |
e2eb7f3a BN |
378 | printf("%4d: %-*s %-*s", i, foff_w, rbuf, |
379 | boff_w, bbuf); | |
380 | if (!is_rt) { | |
381 | agno = map[i + 1].bmv_block / bbperag; | |
382 | agoff = map[i + 1].bmv_block - | |
383 | (agno * bbperag); | |
384 | snprintf(abuf, sizeof(abuf), | |
385 | "(%lld..%lld)", | |
386 | (long long)agoff, | |
387 | (long long)(agoff + | |
388 | map[i + 1].bmv_length - 1LL)); | |
389 | printf(" %*d %-*s", agno_w, agno, | |
390 | aoff_w, abuf); | |
391 | } else | |
392 | printf(" "); | |
393 | printf(" %*lld", tot_w, | |
394 | (long long)map[i+1].bmv_length); | |
66b57340 | 395 | if (flg == FLG_NULL && !pflag) { |
4ca431fc NS |
396 | printf("\n"); |
397 | } else { | |
d371bee3 | 398 | printf(" %-*.*o\n", NFLG, NFLG, flg); |
4ca431fc | 399 | } |
2bd0ea18 NS |
400 | } |
401 | } | |
66b57340 | 402 | if ((flg || pflag) && vflag > 1) { |
d371bee3 | 403 | printf(_(" FLAG Values:\n")); |
7536ce44 DW |
404 | printf(_(" %*.*o Shared extent\n"), |
405 | NFLG+1, NFLG+1, FLG_SHARED); | |
d371bee3 NS |
406 | printf(_(" %*.*o Unwritten preallocated extent\n"), |
407 | NFLG+1, NFLG+1, FLG_PRE); | |
408 | printf(_(" %*.*o Doesn't begin on stripe unit\n"), | |
409 | NFLG+1, NFLG+1, FLG_BSU); | |
410 | printf(_(" %*.*o Doesn't end on stripe unit\n"), | |
411 | NFLG+1, NFLG+1, FLG_ESU); | |
412 | printf(_(" %*.*o Doesn't begin on stripe width\n"), | |
413 | NFLG+1, NFLG+1, FLG_BSW); | |
414 | printf(_(" %*.*o Doesn't end on stripe width\n"), | |
415 | NFLG+1, NFLG+1, FLG_ESW); | |
4ca431fc | 416 | } |
2bd0ea18 NS |
417 | } |
418 | free(map); | |
419 | return 0; | |
420 | } | |
421 | ||
e246ba5f NS |
422 | void |
423 | bmap_init(void) | |
2bd0ea18 | 424 | { |
ad765595 | 425 | bmap_cmd.name = "bmap"; |
e246ba5f NS |
426 | bmap_cmd.cfunc = bmap_f; |
427 | bmap_cmd.argmin = 0; | |
428 | bmap_cmd.argmax = -1; | |
48c46ee3 | 429 | bmap_cmd.flags = CMD_NOMAP_OK; |
4cfe2c37 | 430 | bmap_cmd.args = _("[-acelpv] [-n nx]"); |
e246ba5f NS |
431 | bmap_cmd.oneline = _("print block mapping for an XFS file"); |
432 | bmap_cmd.help = bmap_help; | |
2bd0ea18 | 433 | |
e246ba5f | 434 | add_command(&bmap_cmd); |
2bd0ea18 | 435 | } |