diskman 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []