forked from openebs-archive/node-disk-manager
-
Notifications
You must be signed in to change notification settings - Fork 1
/
common.go
145 lines (121 loc) · 4.84 KB
/
common.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/*
Copyright 2018 The OpenEBS Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// SCSI command definitions.
package smart
import (
"encoding/binary"
"errors"
"fmt"
"regexp"
"strings"
)
var errReadCapacityOverflow = errors.New("READ CAPACITY (10) overflow")
// detectBusType detects the type of bus such as SCSI,Nvme,etc based on the device path given
func detectBusType(dname string) string {
// define regex for identifying a nvme device
RegexNVMeDev := regexp.MustCompile(`^/dev/nvme\d+n\d+$`)
busType := "unknown"
if strings.HasPrefix(dname, "/dev/sd") {
busType = "SCSI"
} else if strings.HasPrefix(dname, "/dev/hd") {
busType = "IDE"
} else if RegexNVMeDev.MatchString(dname) {
busType = "NVMe"
}
return busType
}
// detectSCSIType returns the type of SCSI device such as ATA or SAS by sending
// an inquiry command based on the device path given to it.
func detectSCSIType(name string) (Dev, error) {
device := SCSIDev{DevName: name}
if err := device.Open(); err != nil {
return nil, err
}
// send a scsi inquiry command to the given device
SCSIInquiry, err := device.scsiInquiry()
if err != nil {
return nil, err
}
// Check if device is an ATA device (For an ATA device VendorIdentification value should be equal to ATA)
// For ATA, return pointer to SATA device else return pointer to SCSI device interface
if SCSIInquiry.VendorID == [8]byte{0x41, 0x54, 0x41, 0x20, 0x20, 0x20, 0x20, 0x20} {
return &SATA{device}, nil
}
return &device, nil
}
// readDeviceCapacity sends a SCSI READ CAPACITY(10) command to a device and
// returns the capacity in bytes. If this command fails due to a capacity
// overflow, it retries a SCSI READ CAPACITY(16) command.
func (d *SCSIDev) readDeviceCapacity() (uint64, error) {
if DeviceCapacity, err := d.readDeviceCapacity10(); err == errReadCapacityOverflow {
return d.readDeviceCapacity16()
} else {
return DeviceCapacity, err
}
}
// readDeviceCapacity10 sends a SCSI READ CAPACITY(10) command to a device and
// returns the capacity in bytes.
func (d *SCSIDev) readDeviceCapacity10() (uint64, error) {
respBuf := make([]byte, 8)
// Use cdb10 to send a scsi read capacity command
cdb := CDB10{SCSIReadCapacity10}
// If sending scsi read capacity scsi command fails then return disk capacity
// value 0 with error
if err := d.sendSCSICDB(cdb[:], &respBuf); err != nil {
return 0, err
}
lastLBA := binary.BigEndian.Uint32(respBuf[0:]) // max. addressable LBA
LBsize := binary.BigEndian.Uint32(respBuf[4:]) // logical block size
DeviceCapacity := (uint64(lastLBA) + 1) * uint64(LBsize) // calculate capacity
// The returned lastLBA will be 0xffffffff if the value exceeds the
// maximum representable value.
if lastLBA == 0xffffffff {
return 0, errReadCapacityOverflow
}
return DeviceCapacity, nil
}
// readDeviceCapacity16 sends a SCSI READ CAPACITY(16) command to a device and
// returns the capacity in bytes.
func (d *SCSIDev) readDeviceCapacity16() (uint64, error) {
respBuf := make([]byte, 32)
// Use cdb16 to send a scsi read capacity command
cdb := CDB16{SCSIReadCapacity16, SCSIReadCapacityServiceAction}
binary.BigEndian.PutUint32(cdb[10:], uint32(len(respBuf)))
// If sending scsi read capacity scsi command fails then return disk capacity
// value 0 with error
if err := d.sendSCSICDB(cdb[:], &respBuf); err != nil {
return 0, err
}
lastLBA := binary.BigEndian.Uint64(respBuf[0:]) // max. addressable LBA
LBsize := binary.BigEndian.Uint32(respBuf[8:]) // logical block size
DeviceCapacity := (lastLBA + 1) * uint64(LBsize) // calculate capacity
return DeviceCapacity, nil
}
// isSatisfyCondition checks if the necessary conditions are met or not before getting
// the details for a particular disk such as binary permissions, bus type, etc
func isConditionSatisfied(devPath string) error {
// Check if device path is given or not, if not provided then return with error
if devPath == "" {
return fmt.Errorf("no disk device path given to get the disk details")
}
// Check if required permissions are present or not for accessing a device
if err := CheckBinaryPerm(); err != nil {
return fmt.Errorf("error while checking device access permissions, Error: %+v", err)
}
// Check the type of bus such as SCSI, Nvme, etc
busType := detectBusType(devPath)
if busType != SupportedBusType {
return fmt.Errorf("the device type is not supported yet, device type: %q", busType)
}
return nil
}