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.
@@ -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
@@ -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
@@ -0,0 +1,13 @@
1
+ require 'require_all'
2
+ require 'colorize'
3
+ require 'docopt'
4
+
5
+ require 'ostruct'
6
+
7
+ module Diskman
8
+ def self.root_dir
9
+ File.expand_path('../..', __FILE__)
10
+ end
11
+ end
12
+
13
+ require_rel 'diskman'
@@ -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,9 @@
1
+ module Command
2
+ class Fdisk
3
+ def run
4
+ root_device = RootDevice.choose
5
+ cmd = root_device.get_fdisk_command
6
+ System.exec! cmd, safe: false
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,3 @@
1
+ module Diskman
2
+ VERSION = "1.0.0"
3
+ 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: []