diff --git a/commands.go b/commands.go index fcc36a526..9937e49aa 100644 --- a/commands.go +++ b/commands.go @@ -110,7 +110,7 @@ func runController(c *cli.Context) { if err != nil { Fatalln(err) } - Println(disks) + Println(string(disks)) case "mem": memstats, err := controller.GetMemStats(c.Args().Tail().First()) if err != nil { diff --git a/pkg/controller/client.go b/pkg/controller/client.go index 4f3c479fc..2db2742dc 100644 --- a/pkg/controller/client.go +++ b/pkg/controller/client.go @@ -27,7 +27,7 @@ import ( ) // GetDisks get disks info of the server at given url -func GetDisks(url string) ([]string, error) { +func GetDisks(url string) ([]byte, error) { op := RPCOps{ Method: "DiskInfo.Get", Request: rpc.Args{Request: ""}, @@ -45,7 +45,7 @@ func GetDisks(url string) ([]string, error) { if err := jsonrpc.DecodeClientResponse(resp.Body, &reply); err != nil { return nil, iodine.New(err, nil) } - return reply.Disks, nil + return json.MarshalIndent(reply, "", "\t") } // GetMemStats get memory status of the server at given url diff --git a/pkg/server/rpc/diskinfo.go b/pkg/server/rpc/diskinfo.go index db036a561..181c697be 100644 --- a/pkg/server/rpc/diskinfo.go +++ b/pkg/server/rpc/diskinfo.go @@ -30,8 +30,9 @@ type DiskInfoService struct{} // DiskInfoReply disk info reply for disk info service type DiskInfoReply struct { Hostname string `json:"hostname"` + Mounts map[string]scsi.Mountinfo `json:"mounts"` Disks []string `json:"disks"` - DiskAttributes map[string]scsi.Attributes `json:"disk-attrs"` + DiskAttributes map[string]scsi.Attributes `json:"-"` // for the time being not unmarshalling this } func setDiskInfoReply(sis *DiskInfoReply) error { @@ -40,11 +41,19 @@ func setDiskInfoReply(sis *DiskInfoReply) error { if err != nil { return iodine.New(err, nil) } + mounts, err := scsi.GetMountInfo() + if err != nil { + return iodine.New(err, nil) + } + sis.Mounts = make(map[string]scsi.Mountinfo) + sis.Mounts = mounts + disks, err := scsi.GetDisks() if err != nil { return iodine.New(err, nil) } sis.DiskAttributes = make(map[string]scsi.Attributes) + for k, v := range disks { sis.Disks = append(sis.Disks, k) sis.DiskAttributes[k] = v diff --git a/pkg/utils/scsi/mountinfo.go b/pkg/utils/scsi/mountinfo.go new file mode 100644 index 000000000..2dcc457f7 --- /dev/null +++ b/pkg/utils/scsi/mountinfo.go @@ -0,0 +1,122 @@ +// +build linux + +/* + * Mini Object Storage, (C) 2014 Minio, Inc. + * + * 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. + */ + +package scsi + +import ( + "bufio" + "errors" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" + + "github.com/minio/minio/pkg/iodine" +) + +// Mountinfo container to capture /etc/mtab mount structure +type Mountinfo struct { + FSName string /* name of mounted filesystem */ + Dir string /* filesystem path prefix */ + Type string /* mount type (see mntent.h) */ + Opts string /* mount options (see mntent.h) */ + Freq int /* dump frequency in days */ + Passno int /* pass number on parallel fsck */ +} + +var supportedFSType = map[string]bool{ + "ext4": true, + "xfs": true, + "ext3": true, + "btrfs": true, + "tmpfs": true, + "nfs": true, +} + +func isSupportedType(t string) bool { + _, ok := supportedFSType[t] + return ok +} + +// GetMountInfo - get mount info map +func GetMountInfo() (map[string]Mountinfo, error) { + f, err := os.Open("/etc/mtab") + if err != nil { + return nil, iodine.New(err, nil) + } + mntEnt := make(map[string]Mountinfo) + scanner := bufio.NewScanner(f) + for scanner.Scan() { + mtabEnt := strings.Split(scanner.Text(), " ") + mntInfo := Mountinfo{} + if len(mtabEnt) == 6 { + var err error + if !isSupportedType(mtabEnt[2]) { + continue + } + mntInfo.FSName, err = filepath.EvalSymlinks(mtabEnt[0]) + if err != nil { + continue + } + mntInfo.Dir = mtabEnt[1] + mntInfo.Type = mtabEnt[2] + mntInfo.Opts = mtabEnt[3] + mntInfo.Freq, err = strconv.Atoi(mtabEnt[4]) + if err != nil { + continue + } + mntInfo.Passno, err = strconv.Atoi(mtabEnt[5]) + if err != nil { + continue + } + mntEnt[mntInfo.FSName] = mntInfo + } + } + return mntEnt, nil +} + +// IsUsable provides a comprehensive way of knowing if the provided mountPath is mounted and writable +func IsUsable(mountPath string) (bool, error) { + mntpoint, err := os.Stat(mountPath) + if err != nil { + return false, iodine.New(err, nil) + } + parent, err := os.Stat(filepath.Join(mountPath, "..")) + if err != nil { + return false, iodine.New(err, nil) + } + mntpointSt := mntpoint.Sys().(*syscall.Stat_t) + parentSt := parent.Sys().(*syscall.Stat_t) + + if mntpointSt.Dev == parentSt.Dev { + return false, iodine.New(errors.New("not mounted"), nil) + } + testFile, err := ioutil.TempFile(mountPath, "writetest-") + if err != nil { + return false, iodine.New(err, nil) + } + testFileName := testFile.Name() + // close the file, to avoid leaky fd's + testFile.Close() + if err := os.Remove(testFileName); err != nil { + return false, iodine.New(err, nil) + } + return true, nil +} diff --git a/pkg/utils/scsi/scsi_test.go b/pkg/utils/scsi/scsi_test.go index e8086a554..997e8fb8d 100644 --- a/pkg/utils/scsi/scsi_test.go +++ b/pkg/utils/scsi/scsi_test.go @@ -34,3 +34,8 @@ func (s *MySuite) TestSCSI(c *C) { _, err := GetDisks() c.Assert(err, IsNil) } + +func (s *MySuite) TestMountInfo(c *C) { + _, err := GetMountInfo() + c.Assert(err, IsNil) +}