| /* |
| * blockdev implementation for busybox |
| * |
| * Copyright (C) 2010 Sergey Naumov <sknaumov@gmail.com> |
| * |
| * Licensed under GPLv2, see file LICENSE in this source tree. |
| */ |
| //config:config BLOCKDEV |
| //config: bool "blockdev (2.3 kb)" |
| //config: default y |
| //config: help |
| //config: Performs some ioctls with block devices. |
| |
| //applet:IF_BLOCKDEV(APPLET_NOEXEC(blockdev, blockdev, BB_DIR_SBIN, BB_SUID_DROP, blockdev)) |
| |
| //kbuild:lib-$(CONFIG_BLOCKDEV) += blockdev.o |
| |
| //usage:#define blockdev_trivial_usage |
| //usage: "OPTION BLOCKDEV" |
| //usage:#define blockdev_full_usage "\n\n" |
| //usage: " --setro Set ro" |
| //usage: "\n --setrw Set rw" |
| //usage: "\n --getro Get ro" |
| //usage: "\n --getss Get sector size" |
| //usage: "\n --getbsz Get block size" |
| //usage: "\n --setbsz BYTES Set block size" |
| //usage: "\n --getsz Get device size in 512-byte sectors" |
| ///////: "\n --getsize Get device size in sectors (deprecated)" |
| ///////^^^^^ supported, but not shown in help ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| //usage: "\n --getsize64 Get device size in bytes" |
| //usage: "\n --getra Get readahead in 512-byte sectors" |
| //usage: "\n --setra SECTORS Set readahead" |
| //usage: "\n --flushbufs Flush buffers" |
| //usage: "\n --rereadpt Reread partition table" |
| // util-linux 2.31 also has: |
| // --getdiscardzeroes BLKDISCARDZEROES Get discard zeroes support status |
| // --getpbsz BLKPBSZGET Get physical block (sector) size |
| // --getiomin BLKIOMIN Get minimum I/O size |
| // --getioopt BLKIOOPT Get optimal I/O size |
| // --getalignoff BLKALIGNOFF Get alignment offset in bytes |
| // --getmaxsect BLKSECTGET Get max sectors per request |
| // --setra SECTORS BLKRASET Set readahead |
| // --getra BLKRAGET Get readahead |
| // --setfra SECTORS BLKFRASET Set filesystem readahead |
| // --getfra BLKFRAGET Get filesystem readahead |
| |
| #include "libbb.h" |
| #include <linux/fs.h> |
| |
| /* Takes less space is separate arrays than one array of struct */ |
| static const char bdcmd_names[] ALIGN1 = |
| "setro" "\0" |
| #define CMD_SETRO 0 |
| "setrw" "\0" |
| "getro" "\0" |
| "getss" "\0" |
| "getbsz" "\0" |
| "setbsz" "\0" |
| #define CMD_SETBSZ 5 |
| "getsz" "\0" |
| "getsize" "\0" |
| "getsize64" "\0" |
| "getra" "\0" |
| "setra" "\0" |
| #define CMD_SETRA 10 |
| "flushbufs" "\0" |
| "rereadpt" "\0" |
| ; |
| static const uint32_t bdcmd_ioctl[] ALIGN4 = { |
| BLKROSET, //setro |
| BLKROSET, //setrw |
| BLKROGET, //getro |
| BLKSSZGET, //getss |
| BLKBSZGET, //getbsz |
| BLKBSZSET, //setbsz |
| BLKGETSIZE64, //getsz |
| BLKGETSIZE, //getsize |
| BLKGETSIZE64, //getsize64 |
| BLKRAGET, //getra |
| BLKRASET, //setra |
| BLKFLSBUF, //flushbufs |
| BLKRRPART, //rereadpt |
| }; |
| enum { |
| ARG_NONE = 0, |
| ARG_INT = 1, |
| ARG_ULONG = 2, |
| /* Yes, BLKGETSIZE64 takes pointer to uint64_t, not ullong! */ |
| ARG_U64 = 3, |
| ARG_MASK = 3, |
| |
| FL_USRARG = 4, /* argument is provided by user */ |
| FL_NORESULT = 8, |
| FL_SCALE512 = 16, |
| }; |
| static const uint8_t bdcmd_flags[] ALIGN1 = { |
| ARG_INT + FL_NORESULT, //setro |
| ARG_INT + FL_NORESULT, //setrw |
| ARG_INT, //getro |
| ARG_INT, //getss |
| ARG_INT, //getbsz |
| ARG_INT + FL_NORESULT + FL_USRARG, //setbsz |
| ARG_U64 + FL_SCALE512, //getsz |
| ARG_ULONG, //getsize |
| ARG_U64, //getsize64 |
| ARG_ULONG, //getra |
| ARG_ULONG + FL_NORESULT, //setra |
| ARG_NONE + FL_NORESULT, //flushbufs |
| ARG_NONE + FL_NORESULT, //rereadpt |
| }; |
| |
| static unsigned find_cmd(const char *s) |
| { |
| if (s[0] == '-' && s[1] == '-') { |
| int n = index_in_strings(bdcmd_names, s + 2); |
| if (n >= 0) |
| return n; |
| } |
| bb_show_usage(); |
| } |
| |
| int blockdev_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
| int blockdev_main(int argc UNUSED_PARAM, char **argv) |
| { |
| unsigned bdcmd; |
| unsigned flags; |
| int fd; |
| uint64_t u64; |
| union { |
| int i; |
| unsigned long lu; |
| uint64_t u64; |
| } ioctl_val_on_stack; |
| |
| argv++; |
| if (!argv[0] || !argv[1]) /* must have at least 2 args */ |
| bb_show_usage(); |
| |
| bdcmd = find_cmd(*argv); |
| /* setrw translates to BLKROSET(0), most other ioctls don't care... */ |
| /* ...setro will do BLKROSET(1) */ |
| u64 = (bdcmd == CMD_SETRO); |
| if (bdcmd == CMD_SETBSZ || bdcmd == CMD_SETRA) { |
| /* ...setbsz is BLKBSZSET(bytes) */ |
| /* ...setra is BLKRASET(512 bytes) */ |
| u64 = xatoi_positive(*++argv); |
| } |
| |
| argv++; |
| if (!argv[0] || argv[1]) |
| bb_show_usage(); |
| fd = xopen(argv[0], O_RDONLY); |
| |
| ioctl_val_on_stack.u64 = u64; |
| flags = bdcmd_flags[bdcmd]; |
| #if BB_BIG_ENDIAN |
| /* Store data properly wrt data size. |
| * (1) It's no-op for little-endian. |
| * (2) it's no-op for 0 and -1. |
| * --setro uses arg != 0 and != -1, and it is ARG_INT |
| * --setbsz USER_VAL is also ARG_INT |
| * --setra USER_VAL is ARG_ULONG, but it is passed by value, |
| * not reference (see below in ioctl call). |
| * Thus, we don't need to handle ARG_ULONG. |
| */ |
| switch (flags & ARG_MASK) { |
| case ARG_INT: |
| ioctl_val_on_stack.i = (int)u64; |
| break; |
| # if 0 /* unused */ |
| case ARG_ULONG: |
| ioctl_val_on_stack.lu = (unsigned long)u64; |
| break; |
| # endif |
| } |
| #endif |
| |
| if (ioctl(fd, bdcmd_ioctl[bdcmd], |
| bdcmd == CMD_SETRA |
| ? (void*)(uintptr_t)u64 /* BLKRASET passes _value_, not pointer to it */ |
| : &ioctl_val_on_stack.u64 |
| ) == -1) |
| bb_simple_perror_msg_and_die(*argv); |
| |
| /* Fetch it into register(s) */ |
| u64 = ioctl_val_on_stack.u64; |
| |
| if (flags & FL_SCALE512) |
| u64 >>= 9; |
| |
| /* Zero- or one-extend the value if needed, then print */ |
| switch (flags & (ARG_MASK+FL_NORESULT)) { |
| case ARG_INT: |
| /* Smaller code when we use long long |
| * (gcc tail-merges printf call) |
| */ |
| printf("%lld\n", (long long)(int)u64); |
| break; |
| case ARG_ULONG: |
| u64 = (unsigned long)u64; |
| /* FALLTHROUGH */ |
| case ARG_U64: |
| printf("%llu\n", (unsigned long long)u64); |
| break; |
| } |
| |
| if (ENABLE_FEATURE_CLEAN_UP) |
| close(fd); |
| return EXIT_SUCCESS; |
| } |