diskman 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/diskman +68 -0
- data/lib/diskman.rb +13 -0
- data/lib/diskman/chooser.rb +70 -0
- data/lib/diskman/commands/fdisk.rb +9 -0
- data/lib/diskman/commands/mkfs.rb +30 -0
- data/lib/diskman/commands/write.rb +22 -0
- data/lib/diskman/confirmer.rb +22 -0
- data/lib/diskman/device.rb +39 -0
- data/lib/diskman/mount.rb +21 -0
- data/lib/diskman/root_device.rb +68 -0
- data/lib/diskman/system.rb +40 -0
- data/lib/diskman/version.rb +3 -0
- metadata +139 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6831a1ef183d31bdae66d4ae5080cf3a019b0ace76d259a0783d4751cfca1f94
|
4
|
+
data.tar.gz: 62ac2d6b97e14081766fd44cefbeca8529afcf51ffd2a63c309850b3c58013cc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 314214d528f832e92563265161fbf3e11d8960a04720686084ed4d78361a7f2e2b08e67f8bac3c014f329f088f9d9c6b15b32dc3d9e7a48000780d9b1146e9f9
|
7
|
+
data.tar.gz: 48598af4a378a4190d1292d872133e9c334a214d5d42f02a969b8765079da8ac74f8cdc9c6a8fa82db6c5caae38a8d80b620914542c3905e2176c52210a53608
|
data/bin/diskman
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative '../lib/diskman'
|
3
|
+
include Diskman
|
4
|
+
|
5
|
+
class Main
|
6
|
+
def initialize(opts)
|
7
|
+
@opts = opts
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.run(opts)
|
11
|
+
new(opts).run
|
12
|
+
end
|
13
|
+
|
14
|
+
def params
|
15
|
+
@params ||= OpenStruct.new({
|
16
|
+
version: @opts['--version'],
|
17
|
+
help: @opts['--help'],
|
18
|
+
list: @opts['--list'],
|
19
|
+
write: @opts['write'],
|
20
|
+
fdisk: @opts['fdisk'],
|
21
|
+
mkfs: @opts['mkfs'],
|
22
|
+
file: @opts['<file>'],
|
23
|
+
})
|
24
|
+
end
|
25
|
+
|
26
|
+
def exit_usage
|
27
|
+
puts DOCS
|
28
|
+
exit 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def exit_version
|
32
|
+
puts Diskman::VERSION
|
33
|
+
exit 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
exit_version if params.version
|
38
|
+
exit_usage if params.help
|
39
|
+
|
40
|
+
if params.mkfs
|
41
|
+
Command::Mkfs.new.run(list: params.list)
|
42
|
+
elsif params.write
|
43
|
+
Command::Write.new.run(file: params.file)
|
44
|
+
elsif params.fdisk
|
45
|
+
Command::Fdisk.new.run
|
46
|
+
end
|
47
|
+
rescue Interrupt => e
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
docs = <<~EOF
|
52
|
+
#{'Usage:'.bold}
|
53
|
+
#{File.basename($0)} write <file>
|
54
|
+
#{File.basename($0)} fdisk
|
55
|
+
#{File.basename($0)} mkfs [ --list ]
|
56
|
+
#{File.basename($0)} ( --version | --help )
|
57
|
+
|
58
|
+
#{'Options:'.bold}
|
59
|
+
-v, --version Show version
|
60
|
+
-h, --help Show this help
|
61
|
+
EOF
|
62
|
+
|
63
|
+
begin
|
64
|
+
Main.run(Docopt::docopt(docs, version: VERSION))
|
65
|
+
rescue Docopt::Exit => e
|
66
|
+
puts e.message
|
67
|
+
exit 1
|
68
|
+
end
|
data/lib/diskman.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Diskman
|
2
|
+
# Presents the user with a list of items to choose from.
|
3
|
+
class Chooser
|
4
|
+
def initialize(items, what:)
|
5
|
+
@items = items
|
6
|
+
@singular = what
|
7
|
+
@plural = what + 's'
|
8
|
+
end
|
9
|
+
|
10
|
+
def label
|
11
|
+
if @items.length > 1
|
12
|
+
@plural
|
13
|
+
else
|
14
|
+
@singular
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def space
|
19
|
+
' '
|
20
|
+
end
|
21
|
+
|
22
|
+
def select
|
23
|
+
if @items.length == 0
|
24
|
+
puts 'No %s found'.yellow % @plural
|
25
|
+
raise Interrupt
|
26
|
+
end
|
27
|
+
|
28
|
+
if @items.length == 1
|
29
|
+
puts 'Found the following %s.' % label
|
30
|
+
else
|
31
|
+
puts 'Please pick from the following %s.' % @plural
|
32
|
+
end
|
33
|
+
|
34
|
+
puts
|
35
|
+
|
36
|
+
@items.each_with_index do |device, i|
|
37
|
+
puts "#{(space * 4) }#{i + 1}. #{device}"
|
38
|
+
end
|
39
|
+
|
40
|
+
puts
|
41
|
+
|
42
|
+
if @items.length == 1
|
43
|
+
puts 'Press any key to select it.'
|
44
|
+
$stdin.gets
|
45
|
+
return @items.first
|
46
|
+
end
|
47
|
+
|
48
|
+
puts 'Enter the number of your selection.'
|
49
|
+
puts
|
50
|
+
print '> '
|
51
|
+
|
52
|
+
selection = $stdin.gets.chomp
|
53
|
+
|
54
|
+
puts
|
55
|
+
|
56
|
+
if selection.length == 0
|
57
|
+
raise Interrupt
|
58
|
+
end
|
59
|
+
|
60
|
+
selection = selection.to_i
|
61
|
+
|
62
|
+
if selection <= 0 || selection > @items.length
|
63
|
+
puts 'Invalid selection'.red
|
64
|
+
raise Interrupt
|
65
|
+
end
|
66
|
+
|
67
|
+
@items[selection - 1]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Command
|
2
|
+
class Mkfs
|
3
|
+
def get_list
|
4
|
+
Dir['/usr/bin/mkfs.*'].map do |path|
|
5
|
+
path.gsub(%r[^/usr/bin/mkfs.], '')
|
6
|
+
end.sort
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(list: false)
|
10
|
+
if list
|
11
|
+
puts get_list
|
12
|
+
return
|
13
|
+
end
|
14
|
+
|
15
|
+
device = RootDevice.choose
|
16
|
+
device.ensure_not_mounted!
|
17
|
+
|
18
|
+
device = device.choose_block_device
|
19
|
+
|
20
|
+
fs = Chooser.new(get_list, what: 'filesystem').select
|
21
|
+
cmd = device.get_mkfs_command(fs)
|
22
|
+
|
23
|
+
puts "Filesystem: #{fs.yellow}"
|
24
|
+
puts "Device: #{device.to_s.yellow}"
|
25
|
+
puts "Command: #{cmd.yellow}"
|
26
|
+
|
27
|
+
System.exec!(cmd)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Command
|
2
|
+
class Write
|
3
|
+
def run(file:)
|
4
|
+
if !File.exists?(file)
|
5
|
+
puts ('Unable to read ' + file).red
|
6
|
+
raise Interrupt
|
7
|
+
end
|
8
|
+
|
9
|
+
device = RootDevice.choose
|
10
|
+
device.ensure_not_mounted!
|
11
|
+
|
12
|
+
size = File.size(file)
|
13
|
+
cmd = device.get_write_command(file, size)
|
14
|
+
|
15
|
+
puts "File: #{file.yellow} (#{System.bytes2human(size)})"
|
16
|
+
puts "Device: #{device.to_s.yellow}"
|
17
|
+
puts "Command: #{cmd.yellow}"
|
18
|
+
|
19
|
+
System.exec!(cmd)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Diskman
|
2
|
+
# Confirms whether the user wants to do something destructive.
|
3
|
+
class Confirmer
|
4
|
+
YES = 'YES'
|
5
|
+
|
6
|
+
def self.check!
|
7
|
+
self.new.check!
|
8
|
+
end
|
9
|
+
|
10
|
+
def check!
|
11
|
+
puts
|
12
|
+
puts 'Are you sure? Type "%s" if so.' % YES
|
13
|
+
puts
|
14
|
+
|
15
|
+
print '> '
|
16
|
+
|
17
|
+
if $stdin.gets.chomp != YES
|
18
|
+
raise Interrupt
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Diskman
|
2
|
+
class Device
|
3
|
+
attr_reader :path
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
@path = '/dev/' + name
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_fdisk_command
|
11
|
+
'sudo fdisk %s' % @path
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_mkfs_command(fs)
|
15
|
+
'sudo mkfs.%s %s' % [fs, @path]
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_write_command(path, bytes)
|
19
|
+
if `which 2>/dev/null pv`.length > 0
|
20
|
+
pv = 'pv --size %d' % bytes
|
21
|
+
dd = 'dd if="%s" | ' + pv + ' | sudo dd of="%s" bs=%d'
|
22
|
+
else
|
23
|
+
dd = 'sudo dd if="%s" of="%s" bs=%d'
|
24
|
+
end
|
25
|
+
|
26
|
+
dd % [path, @path, 4096]
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
@path
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def <=>(o)
|
36
|
+
@path <=> o.path
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Diskman
|
2
|
+
class Mount
|
3
|
+
def self.is_mounted(name)
|
4
|
+
Mount.new.find(name)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.get_mount_point(name)
|
8
|
+
Mount.new.find(name).chomp.match(/[^ ]+ (?<m>[^ ]+)/)[:m]
|
9
|
+
end
|
10
|
+
|
11
|
+
def find(name)
|
12
|
+
lines.grep(%r[^/dev/#{name}\d?\s+]).first
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def lines
|
18
|
+
@lines ||= File.read('/etc/mtab').lines
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Diskman
|
2
|
+
class RootDevice < Device
|
3
|
+
def self.get_removable
|
4
|
+
Dir['/sys/block/*/removable'].select do |file|
|
5
|
+
File.read(file).strip == '1'
|
6
|
+
end.map do |path|
|
7
|
+
path =~ /^\/sys\/block\/(.*)\/removable$/ && RootDevice.new($1)
|
8
|
+
end.sort
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.choose
|
12
|
+
Chooser.new(RootDevice.get_removable, what: 'removable device').select
|
13
|
+
end
|
14
|
+
|
15
|
+
def get_devices
|
16
|
+
Dir[@path + '*'].sort.map do |file|
|
17
|
+
file =~ %r[/dev/(.*)] && Device.new($1)
|
18
|
+
end.sort
|
19
|
+
end
|
20
|
+
|
21
|
+
def choose_block_device
|
22
|
+
Chooser.new(get_devices, what: 'device').select
|
23
|
+
end
|
24
|
+
|
25
|
+
def ensure_not_mounted!
|
26
|
+
if mounted?
|
27
|
+
puts ('Warning: device appears to be mounted at ' + get_mount_point).yellow
|
28
|
+
puts 'Not continuing'.red
|
29
|
+
raise Interrupt
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def mounted?
|
34
|
+
Mount.is_mounted(@name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_mount_point
|
38
|
+
Mount.get_mount_point(@name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
@path + ' [' + [size, label].reject(&:empty?).join(', ') + ']'
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def get_int_prop(name)
|
48
|
+
get_prop(name).to_i
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_prop(name)
|
52
|
+
file = "/sys/class/block/%s/%s" % [@name, name]
|
53
|
+
File.read(file).strip.gsub(/\s+/, ' ')
|
54
|
+
end
|
55
|
+
|
56
|
+
def label
|
57
|
+
[get_prop('device/vendor'), get_prop('device/model')].reject(&:empty?).join(' ')
|
58
|
+
end
|
59
|
+
|
60
|
+
def size_bytes
|
61
|
+
get_int_prop('size') * get_int_prop('queue/logical_block_size')
|
62
|
+
end
|
63
|
+
|
64
|
+
def size
|
65
|
+
System.bytes2human(size_bytes)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Diskman
|
2
|
+
class System
|
3
|
+
# If sudo prompts for the password when a pipeline with pv has already
|
4
|
+
# started then we're unable to enter the password. Run a pointless
|
5
|
+
# command with sudo first to ensure that we can accept keyboard input
|
6
|
+
# for the password, if necessary.
|
7
|
+
def self.prepare_sudo_session!
|
8
|
+
system('sudo echo >/dev/null')
|
9
|
+
end
|
10
|
+
|
11
|
+
# Execute a command.
|
12
|
+
# If sudo is true, ensures sudo is ready before running the command.
|
13
|
+
# If safe is true, ensures the user definitely wants to run the command
|
14
|
+
# before running it.
|
15
|
+
def self.exec!(cmd, safe: true, sudo: true)
|
16
|
+
Confirmer.check! if safe
|
17
|
+
prepare_sudo_session! if sudo
|
18
|
+
puts
|
19
|
+
exec cmd
|
20
|
+
end
|
21
|
+
|
22
|
+
# Convert bytes into a human-friendly representation.
|
23
|
+
def self.bytes2human(b)
|
24
|
+
return '0B' if b <= 0
|
25
|
+
|
26
|
+
# Use 1000 to match the misleading way disk capacities are
|
27
|
+
# advertised.
|
28
|
+
k = 1000
|
29
|
+
|
30
|
+
suffices = ['T', 'G', 'M', 'K', 'B']
|
31
|
+
|
32
|
+
suffices.each_with_index do |suffix, i|
|
33
|
+
threshold = k ** (suffices.length - i - 1)
|
34
|
+
if b >= threshold
|
35
|
+
return (b / threshold).to_s + suffix
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: diskman
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- crdx
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-11-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: require_all
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: colorize
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.8.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.8.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: docopt
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.6.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.6.1
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.17.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.17.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.8'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.8'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '12.3'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '12.3'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
executables:
|
100
|
+
- diskman
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- bin/diskman
|
105
|
+
- lib/diskman.rb
|
106
|
+
- lib/diskman/chooser.rb
|
107
|
+
- lib/diskman/commands/fdisk.rb
|
108
|
+
- lib/diskman/commands/mkfs.rb
|
109
|
+
- lib/diskman/commands/write.rb
|
110
|
+
- lib/diskman/confirmer.rb
|
111
|
+
- lib/diskman/device.rb
|
112
|
+
- lib/diskman/mount.rb
|
113
|
+
- lib/diskman/root_device.rb
|
114
|
+
- lib/diskman/system.rb
|
115
|
+
- lib/diskman/version.rb
|
116
|
+
homepage: https://github.com/crdx/diskman
|
117
|
+
licenses:
|
118
|
+
- MIT
|
119
|
+
metadata: {}
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
requirements: []
|
135
|
+
rubygems_version: 3.0.6
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Interactive command line interface for safely managing disks
|
139
|
+
test_files: []
|