disk.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. package v1
  2. import (
  3. "fmt"
  4. "net/http"
  5. "reflect"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "github.com/IceWhaleTech/CasaOS/model"
  10. "github.com/IceWhaleTech/CasaOS/pkg/config"
  11. "github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
  12. "github.com/IceWhaleTech/CasaOS/pkg/utils/file"
  13. "github.com/IceWhaleTech/CasaOS/service"
  14. model2 "github.com/IceWhaleTech/CasaOS/service/model"
  15. "github.com/gin-gonic/gin"
  16. "github.com/shirou/gopsutil/v3/disk"
  17. )
  18. var diskMap = make(map[string]string)
  19. // @Summary disk list
  20. // @Produce application/json
  21. // @Accept application/json
  22. // @Tags disk
  23. // @Security ApiKeyAuth
  24. // @Success 200 {string} string "ok"
  25. // @Router /disk/list [get]
  26. func GetDiskList(c *gin.Context) {
  27. list := service.MyService.Disk().LSBLK(false)
  28. dbList := service.MyService.Disk().GetSerialAll()
  29. part := make(map[string]int64, len(dbList))
  30. for _, v := range dbList {
  31. part[v.MountPoint] = v.CreatedAt
  32. }
  33. findSystem := 0
  34. disks := []model.Drive{}
  35. storage := []model.Storage{}
  36. avail := []model.Drive{}
  37. for i := 0; i < len(list); i++ {
  38. disk := model.Drive{}
  39. if list[i].Rota {
  40. disk.DiskType = "HDD"
  41. } else {
  42. disk.DiskType = "SSD"
  43. }
  44. disk.Serial = list[i].Serial
  45. disk.Name = list[i].Name
  46. disk.Size = list[i].Size
  47. disk.Path = list[i].Path
  48. disk.Model = list[i].Model
  49. if len(list[i].Children) > 0 && findSystem == 0 {
  50. for j := 0; j < len(list[i].Children); j++ {
  51. if len(list[i].Children[j].Children) > 0 {
  52. for _, v := range list[i].Children[j].Children {
  53. if v.MountPoint == "/" {
  54. stor := model.Storage{}
  55. stor.Name = "System"
  56. stor.MountPoint = v.MountPoint
  57. stor.Size = v.FSSize
  58. stor.Avail = v.FSAvail
  59. stor.Path = v.Path
  60. stor.Type = v.FsType
  61. stor.DriveName = "System"
  62. disk.Model = "System"
  63. if strings.Contains(v.SubSystems, "mmc") {
  64. disk.DiskType = "MMC"
  65. } else if strings.Contains(v.SubSystems, "usb") {
  66. disk.DiskType = "USB"
  67. }
  68. disk.Health = "true"
  69. disks = append(disks, disk)
  70. storage = append(storage, stor)
  71. findSystem = 1
  72. break
  73. }
  74. }
  75. } else {
  76. if list[i].Children[j].MountPoint == "/" {
  77. stor := model.Storage{}
  78. stor.Name = "System"
  79. stor.MountPoint = list[i].Children[j].MountPoint
  80. stor.Size = list[i].Children[j].FSSize
  81. stor.Avail = list[i].Children[j].FSAvail
  82. stor.Path = list[i].Children[j].Path
  83. stor.Type = list[i].Children[j].FsType
  84. stor.DriveName = "System"
  85. disk.Model = "System"
  86. if strings.Contains(list[i].Children[j].SubSystems, "mmc") {
  87. disk.DiskType = "MMC"
  88. } else if strings.Contains(list[i].Children[j].SubSystems, "usb") {
  89. disk.DiskType = "USB"
  90. }
  91. disk.Health = "true"
  92. disks = append(disks, disk)
  93. storage = append(storage, stor)
  94. findSystem = 1
  95. break
  96. }
  97. }
  98. }
  99. }
  100. if findSystem == 1 {
  101. findSystem += 1
  102. continue
  103. }
  104. if list[i].Tran == "sata" || list[i].Tran == "nvme" || list[i].Tran == "spi" || list[i].Tran == "sas" || strings.Contains(list[i].SubSystems, "virtio") || list[i].Tran == "ata" {
  105. temp := service.MyService.Disk().SmartCTL(list[i].Path)
  106. if reflect.DeepEqual(temp, model.SmartctlA{}) {
  107. temp.SmartStatus.Passed = true
  108. }
  109. if len(list[i].Children) == 1 && len(list[i].Children[0].MountPoint) > 0 {
  110. stor := model.Storage{}
  111. stor.MountPoint = list[i].Children[0].MountPoint
  112. stor.Size = list[i].Children[0].FSSize
  113. stor.Avail = list[i].Children[0].FSAvail
  114. stor.Path = list[i].Children[0].Path
  115. stor.Type = list[i].Children[0].FsType
  116. stor.DriveName = list[i].Name
  117. pathArr := strings.Split(list[i].Children[0].MountPoint, "/")
  118. if len(pathArr) == 3 {
  119. stor.Name = pathArr[2]
  120. }
  121. if t, ok := part[list[i].Children[0].MountPoint]; ok {
  122. stor.CreatedAt = t
  123. }
  124. storage = append(storage, stor)
  125. } else {
  126. //todo 长度有问题
  127. if len(list[i].Children) == 1 && list[i].Children[0].FsType == "ext4" {
  128. disk.NeedFormat = false
  129. avail = append(avail, disk)
  130. } else {
  131. disk.NeedFormat = true
  132. avail = append(avail, disk)
  133. }
  134. }
  135. disk.Temperature = temp.Temperature.Current
  136. disk.Health = strconv.FormatBool(temp.SmartStatus.Passed)
  137. disks = append(disks, disk)
  138. }
  139. }
  140. data := make(map[string]interface{}, 3)
  141. data["drive"] = disks
  142. data["storage"] = storage
  143. data["avail"] = avail
  144. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
  145. }
  146. // @Summary get disk list
  147. // @Produce application/json
  148. // @Accept application/json
  149. // @Tags disk
  150. // @Security ApiKeyAuth
  151. // @Success 200 {string} string "ok"
  152. // @Router /disk/lists [get]
  153. func GetPlugInDisks(c *gin.Context) {
  154. list := service.MyService.Disk().LSBLK(true)
  155. var result []*disk.UsageStat
  156. for _, item := range list {
  157. result = append(result, service.MyService.Disk().GetDiskInfoByPath(item.Path))
  158. }
  159. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: result})
  160. }
  161. // @Summary disk detail
  162. // @Produce application/json
  163. // @Accept application/json
  164. // @Tags disk
  165. // @Security ApiKeyAuth
  166. // @Param path query string true "for example /dev/sda"
  167. // @Success 200 {string} string "ok"
  168. // @Router /disk/info [get]
  169. func GetDiskInfo(c *gin.Context) {
  170. path := c.Query("path")
  171. if len(path) == 0 {
  172. c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
  173. }
  174. m := service.MyService.Disk().GetDiskInfo(path)
  175. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: m})
  176. }
  177. // @Summary format storage
  178. // @Produce application/json
  179. // @Accept multipart/form-data
  180. // @Tags disk
  181. // @Security ApiKeyAuth
  182. // @Param path formData string true "e.g. /dev/sda1"
  183. // @Param pwd formData string true "user password"
  184. // @Param volume formData string true "mount point"
  185. // @Success 200 {string} string "ok"
  186. // @Router /disk/format [post]
  187. func PostDiskFormat(c *gin.Context) {
  188. path := c.PostForm("path")
  189. t := "ext4"
  190. pwd := c.PostForm("pwd")
  191. volume := c.PostForm("volume")
  192. if pwd != config.UserInfo.PWD {
  193. c.JSON(http.StatusOK, model.Result{Success: common_err.PWD_INVALID, Message: common_err.GetMsg(common_err.PWD_INVALID)})
  194. return
  195. }
  196. if len(path) == 0 || len(t) == 0 {
  197. c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
  198. return
  199. }
  200. if _, ok := diskMap[path]; ok {
  201. c.JSON(http.StatusOK, model.Result{Success: common_err.DISK_BUSYING, Message: common_err.GetMsg(common_err.DISK_BUSYING)})
  202. return
  203. }
  204. diskMap[path] = "busying"
  205. service.MyService.Disk().UmountPointAndRemoveDir(path)
  206. format := service.MyService.Disk().FormatDisk(path, t)
  207. if len(format) == 0 {
  208. c.JSON(http.StatusOK, model.Result{Success: common_err.FORMAT_ERROR, Message: common_err.GetMsg(common_err.FORMAT_ERROR)})
  209. delete(diskMap, path)
  210. return
  211. }
  212. service.MyService.Disk().MountDisk(path, volume)
  213. service.MyService.Disk().RemoveLSBLKCache()
  214. delete(diskMap, path)
  215. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
  216. }
  217. // @Summary 获取支持的格式
  218. // @Produce application/json
  219. // @Accept application/json
  220. // @Tags disk
  221. // @Security ApiKeyAuth
  222. // @Success 200 {string} string "ok"
  223. // @Router /disk/type [get]
  224. func FormatDiskType(c *gin.Context) {
  225. var strArr = [4]string{"fat32", "ntfs", "ext4", "exfat"}
  226. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: strArr})
  227. }
  228. // @Summary 删除分区
  229. // @Produce application/json
  230. // @Accept multipart/form-data
  231. // @Tags disk
  232. // @Security ApiKeyAuth
  233. // @Param path formData string true "磁盘路径 例如/dev/sda1"
  234. // @Success 200 {string} string "ok"
  235. // @Router /disk/delpart [delete]
  236. func RemovePartition(c *gin.Context) {
  237. path := c.PostForm("path")
  238. if len(path) == 0 {
  239. c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
  240. }
  241. var p = path[:len(path)-1]
  242. var n = path[len(path)-1:]
  243. service.MyService.Disk().DelPartition(p, n)
  244. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
  245. }
  246. // @Summary add storage
  247. // @Produce application/json
  248. // @Accept multipart/form-data
  249. // @Tags disk
  250. // @Security ApiKeyAuth
  251. // @Param path formData string true "disk path e.g. /dev/sda"
  252. // @Param serial formData string true "serial"
  253. // @Param name formData string true "name"
  254. // @Param format formData bool true "need format(true)"
  255. // @Success 200 {string} string "ok"
  256. // @Router /disk/storage [post]
  257. func PostDiskAddPartition(c *gin.Context) {
  258. name := c.PostForm("name")
  259. path := c.PostForm("path")
  260. format, _ := strconv.ParseBool(c.PostForm("format"))
  261. if len(name) == 0 || len(path) == 0 {
  262. c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
  263. return
  264. }
  265. if _, ok := diskMap[path]; ok {
  266. c.JSON(http.StatusOK, model.Result{Success: common_err.DISK_BUSYING, Message: common_err.GetMsg(common_err.DISK_BUSYING)})
  267. return
  268. }
  269. if !file.CheckNotExist("/DATA/" + name) {
  270. // /mnt/name exist
  271. c.JSON(http.StatusOK, model.Result{Success: common_err.NAME_NOT_AVAILABLE, Message: common_err.GetMsg(common_err.NAME_NOT_AVAILABLE)})
  272. return
  273. }
  274. diskMap[path] = "busying"
  275. currentDisk := service.MyService.Disk().GetDiskInfo(path)
  276. if !format {
  277. if len(currentDisk.Children) != 1 || !(len(currentDisk.Children) > 0 && currentDisk.Children[0].FsType == "ext4") {
  278. c.JSON(http.StatusOK, model.Result{Success: common_err.DISK_NEEDS_FORMAT, Message: common_err.GetMsg(common_err.DISK_NEEDS_FORMAT)})
  279. delete(diskMap, path)
  280. return
  281. }
  282. } else {
  283. service.MyService.Disk().AddPartition(path)
  284. }
  285. formatBool := true
  286. for formatBool {
  287. currentDisk = service.MyService.Disk().GetDiskInfo(path)
  288. fmt.Println(currentDisk.Children)
  289. if len(currentDisk.Children) > 0 {
  290. formatBool = false
  291. break
  292. }
  293. time.Sleep(time.Second)
  294. }
  295. currentDisk = service.MyService.Disk().GetDiskInfo(path)
  296. if len(currentDisk.Children) != 1 {
  297. c.JSON(http.StatusOK, model.Result{Success: common_err.DISK_NEEDS_FORMAT, Message: common_err.GetMsg(common_err.DISK_NEEDS_FORMAT)})
  298. return
  299. }
  300. mountPath := "/DATA/" + name
  301. m := model2.SerialDisk{}
  302. m.MountPoint = mountPath
  303. m.Path = currentDisk.Children[0].Path
  304. m.UUID = currentDisk.Children[0].UUID
  305. m.State = 0
  306. m.CreatedAt = time.Now().Unix()
  307. service.MyService.Disk().SaveMountPoint(m)
  308. //mount dir
  309. service.MyService.Disk().MountDisk(currentDisk.Children[0].Path, mountPath)
  310. service.MyService.Disk().RemoveLSBLKCache()
  311. delete(diskMap, path)
  312. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
  313. }
  314. // @Summary add mount point
  315. // @Produce application/json
  316. // @Accept multipart/form-data
  317. // @Tags disk
  318. // @Security ApiKeyAuth
  319. // @Param path formData string true "for example: /dev/sda1"
  320. // @Param serial formData string true "disk id"
  321. // @Success 200 {string} string "ok"
  322. // @Router /disk/mount [post]
  323. func PostMountDisk(c *gin.Context) {
  324. // for example: path=/dev/sda1
  325. path := c.PostForm("path")
  326. serial := c.PostForm("serial")
  327. mountPath := "/DATA/volume"
  328. var list = service.MyService.Disk().GetSerialAll()
  329. var pathMapList = make(map[string]string, len(list))
  330. for _, v := range list {
  331. pathMapList[v.MountPoint] = "1"
  332. }
  333. for i := 0; i < len(list)+1; i++ {
  334. if _, ok := pathMapList[mountPath+strconv.Itoa(i)]; !ok {
  335. mountPath = mountPath + strconv.Itoa(i)
  336. break
  337. }
  338. }
  339. //mount dir
  340. service.MyService.Disk().MountDisk(path, mountPath)
  341. m := model2.SerialDisk{}
  342. m.MountPoint = mountPath
  343. m.Path = path
  344. m.UUID = serial
  345. m.State = 0
  346. //service.MyService.Disk().SaveMountPoint(m)
  347. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
  348. }
  349. // @Summary remove mount point
  350. // @Produce application/json
  351. // @Accept multipart/form-data
  352. // @Tags disk
  353. // @Security ApiKeyAuth
  354. // @Param path formData string true "e.g. /dev/sda1"
  355. // @Param mount_point formData string true "e.g. /mnt/volume1"
  356. // @Param pwd formData string true "user password"
  357. // @Success 200 {string} string "ok"
  358. // @Router /disk/umount [post]
  359. func PostDiskUmount(c *gin.Context) {
  360. path := c.PostForm("path")
  361. mountPoint := c.PostForm("volume")
  362. pwd := c.PostForm("pwd")
  363. if len(path) == 0 || len(mountPoint) == 0 {
  364. c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
  365. return
  366. }
  367. if pwd != config.UserInfo.PWD {
  368. c.JSON(http.StatusOK, model.Result{Success: common_err.PWD_INVALID, Message: common_err.GetMsg(common_err.PWD_INVALID)})
  369. return
  370. }
  371. if _, ok := diskMap[path]; ok {
  372. c.JSON(http.StatusOK, model.Result{Success: common_err.DISK_BUSYING, Message: common_err.GetMsg(common_err.DISK_BUSYING)})
  373. return
  374. }
  375. service.MyService.Disk().UmountPointAndRemoveDir(path)
  376. //delete data
  377. service.MyService.Disk().DeleteMountPoint(path, mountPoint)
  378. service.MyService.Disk().RemoveLSBLKCache()
  379. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
  380. }
  381. // @Summary confirm delete disk
  382. // @Produce application/json
  383. // @Accept application/json
  384. // @Tags disk
  385. // @Security ApiKeyAuth
  386. // @Param id path string true "id"
  387. // @Success 200 {string} string "ok"
  388. // @Router /disk/remove/{id} [delete]
  389. func DeleteDisk(c *gin.Context) {
  390. id := c.Param("id")
  391. service.MyService.Disk().DeleteMount(id)
  392. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
  393. }
  394. // @Summary check mount point
  395. // @Produce application/json
  396. // @Accept application/json
  397. // @Tags disk
  398. // @Security ApiKeyAuth
  399. // @Success 200 {string} string "ok"
  400. // @Router /disk/init [get]
  401. func GetDiskCheck(c *gin.Context) {
  402. dbList := service.MyService.Disk().GetSerialAll()
  403. list := service.MyService.Disk().LSBLK(true)
  404. mapList := make(map[string]string)
  405. for _, v := range list {
  406. mapList[v.Serial] = "1"
  407. }
  408. for _, v := range dbList {
  409. if _, ok := mapList[v.UUID]; !ok {
  410. //disk undefind
  411. c.JSON(http.StatusOK, model.Result{Success: common_err.ERROR, Message: common_err.GetMsg(common_err.ERROR), Data: "disk undefind"})
  412. return
  413. }
  414. }
  415. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
  416. }
  417. // @Summary check mount point
  418. // @Produce application/json
  419. // @Accept application/json
  420. // @Tags disk
  421. // @Security ApiKeyAuth
  422. // @Success 200 {string} string "ok"
  423. // @Router /disk/usb [get]
  424. func GetUSBList(c *gin.Context) {
  425. list := service.MyService.Disk().LSBLK(false)
  426. data := []model.DriveUSB{}
  427. for _, v := range list {
  428. if v.Tran == "usb" {
  429. temp := model.DriveUSB{}
  430. temp.Model = v.Model
  431. temp.Name = v.Name
  432. temp.Size = v.Size
  433. mountTemp := true
  434. if len(v.Children) == 0 {
  435. mountTemp = false
  436. }
  437. for _, child := range v.Children {
  438. if len(child.MountPoint) > 0 {
  439. avail, _ := strconv.ParseUint(child.FSAvail, 10, 64)
  440. temp.Avail += avail
  441. } else {
  442. mountTemp = false
  443. }
  444. }
  445. temp.Mount = mountTemp
  446. data = append(data, temp)
  447. }
  448. }
  449. c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
  450. }