diskmg.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. package diskmg
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "log"
  6. "net/http"
  7. "os/exec"
  8. "path/filepath"
  9. "regexp"
  10. "runtime"
  11. "strings"
  12. "time"
  13. db "imuslab.com/arozos/mod/database"
  14. fs "imuslab.com/arozos/mod/filesystem"
  15. "imuslab.com/arozos/mod/utils"
  16. )
  17. type Lsblk struct {
  18. Blockdevices []struct {
  19. Name string `json:"name"`
  20. MajMin string `json:"maj:min"`
  21. Rm bool `json:"rm"`
  22. Size int64 `json:"size"`
  23. Ro bool `json:"ro"`
  24. Type string `json:"type"`
  25. Mountpoint interface{} `json:"mountpoint"`
  26. Children []struct {
  27. Name string `json:"name"`
  28. MajMin string `json:"maj:min"`
  29. Rm bool `json:"rm"`
  30. Size int64 `json:"size"`
  31. Ro bool `json:"ro"`
  32. Type string `json:"type"`
  33. Mountpoint string `json:"mountpoint"`
  34. } `json:"children"`
  35. } `json:"blockdevices"`
  36. }
  37. type LsblkF struct {
  38. Blockdevices []struct {
  39. Name string `json:"name"`
  40. Fstype interface{} `json:"fstype"`
  41. Label interface{} `json:"label"`
  42. UUID interface{} `json:"uuid"`
  43. Fsavail interface{} `json:"fsavail"`
  44. Fsuse interface{} `json:"fsuse%"`
  45. Mountpoint interface{} `json:"mountpoint"`
  46. Children []struct {
  47. Name string `json:"name"`
  48. Fstype string `json:"fstype"`
  49. Label interface{} `json:"label"`
  50. UUID string `json:"uuid"`
  51. Fsavail string `json:"fsavail"`
  52. Fsuse string `json:"fsuse%"`
  53. Mountpoint string `json:"mountpoint"`
  54. } `json:"children"`
  55. } `json:"blockdevices"`
  56. }
  57. var (
  58. supportedFormats = []string{"ntfs", "vfat", "ext4", "ext3", "btrfs"}
  59. )
  60. /*
  61. Diskmg View Generator
  62. This section of the code is a direct translation of the original
  63. AOB's diskmg.php and diskmgWin.php.
  64. If you find any bugs in these code, just remember they are legacy
  65. code and rewriting the whole thing will save you a lot more time.
  66. */
  67. func HandleView(w http.ResponseWriter, r *http.Request) {
  68. partition, _ := utils.Mv(r, "partition", false)
  69. detailMode := (partition != "")
  70. if runtime.GOOS == "windows" {
  71. //Windows. Use DiskmgWin binary
  72. if utils.FileExists("./system/disk/diskmg/DiskmgWin.exe") {
  73. out := ""
  74. if detailMode {
  75. cmd := exec.Command("./system/disk/diskmg/DiskmgWin.exe", "-d")
  76. o, err := cmd.CombinedOutput()
  77. if err != nil {
  78. utils.SendErrorResponse(w, "Permission Denied")
  79. return
  80. }
  81. out = string(o)
  82. } else {
  83. cmd := exec.Command("./system/disk/diskmg/DiskmgWin.exe")
  84. o, err := cmd.CombinedOutput()
  85. if err != nil {
  86. utils.SendErrorResponse(w, "Permission Denied")
  87. return
  88. }
  89. out = string(o)
  90. }
  91. out = strings.TrimSpace(out)
  92. lines := strings.Split(out, ";")
  93. results := [][]string{}
  94. for _, line := range lines {
  95. data := strings.Split(line, ",")
  96. if len(data) > 0 && data[0] != "" {
  97. results = append(results, data)
  98. }
  99. }
  100. js, _ := json.Marshal(results)
  101. utils.SendJSONResponse(w, string(js))
  102. } else {
  103. log.Println("system/disk/diskmg/DiskmgWin.exe NOT FOUND. Unable to load Window's disk information")
  104. utils.SendErrorResponse(w, "DiskmgWin.exe not found")
  105. return
  106. }
  107. } else {
  108. //Linux. Use lsblk and df to check volume info
  109. partition := new(Lsblk)
  110. format := new(LsblkF)
  111. df := ""
  112. //Get partition information
  113. cmd := exec.Command("lsblk", "-b", "--json")
  114. o, err := cmd.CombinedOutput()
  115. if err != nil {
  116. utils.SendErrorResponse(w, err.Error())
  117. return
  118. }
  119. err = json.Unmarshal(o, &partition)
  120. if err != nil {
  121. utils.SendErrorResponse(w, err.Error())
  122. return
  123. }
  124. //Get format info
  125. cmd = exec.Command("lsblk", "-f", "-b", "--json")
  126. o, err = cmd.CombinedOutput()
  127. if err != nil {
  128. utils.SendErrorResponse(w, err.Error())
  129. return
  130. }
  131. err = json.Unmarshal(o, &format)
  132. if err != nil {
  133. utils.SendErrorResponse(w, err.Error())
  134. return
  135. }
  136. //Get df info
  137. cmd = exec.Command("df")
  138. o, err = cmd.CombinedOutput()
  139. if err != nil {
  140. utils.SendErrorResponse(w, err.Error())
  141. return
  142. }
  143. df = string(o)
  144. //Filter the df information
  145. for strings.Contains(df, " ") {
  146. df = strings.ReplaceAll(df, " ", " ")
  147. }
  148. dflines := strings.Split(df, "\n")
  149. parsedDf := [][]string{}
  150. for _, line := range dflines {
  151. linedata := strings.Split(line, " ")
  152. parsedDf = append(parsedDf, linedata)
  153. }
  154. //Throw away the table header
  155. parsedDf = parsedDf[1:]
  156. js, _ := json.Marshal([]interface{}{
  157. partition,
  158. format,
  159. parsedDf,
  160. })
  161. utils.SendJSONResponse(w, string(js))
  162. }
  163. }
  164. /*
  165. Mounting a given partition or devices
  166. Manual translated from mountTool.php
  167. Require GET parameter: dev / format / mnt
  168. */
  169. func HandleMount(w http.ResponseWriter, r *http.Request, fsHandlers []*fs.FileSystemHandler) {
  170. if runtime.GOOS == "linux" {
  171. targetDev, _ := utils.Mv(r, "dev", false)
  172. format, err := utils.Mv(r, "format", false)
  173. if err != nil {
  174. utils.SendErrorResponse(w, "format not defined")
  175. return
  176. }
  177. mountPt, err := utils.Mv(r, "mnt", false)
  178. if err != nil {
  179. utils.SendErrorResponse(w, "Mount Point not defined")
  180. return
  181. }
  182. //Check if device is valid
  183. ok, devID := checkDeviceValid(targetDev)
  184. if !ok {
  185. utils.SendErrorResponse(w, "Device name is not valid")
  186. return
  187. }
  188. //Check if the given format is supported
  189. mountingTool := ""
  190. if format == "ntfs" {
  191. mountingTool = "ntfs-3g"
  192. } else if format == "ext4" {
  193. mountingTool = "ext4"
  194. } else if format == "vfat" {
  195. mountingTool = "vfat"
  196. } else if format == "brtfs" {
  197. mountingTool = "brtfs"
  198. } else {
  199. utils.SendErrorResponse(w, "Format not supported")
  200. return
  201. }
  202. //Check if mount point exists, only support /medoa/*
  203. safeMountPoint := filepath.Clean(strings.ReplaceAll(mountPt, "../", ""))
  204. if !utils.FileExists(safeMountPoint) {
  205. utils.SendErrorResponse(w, "Mount point not exists, given: "+safeMountPoint)
  206. return
  207. }
  208. //Check if action is mount or umount
  209. umount, _ := utils.Mv(r, "umount", false)
  210. if umount == "true" {
  211. //Unmount the given mountpoint
  212. output, err := Unmount(safeMountPoint, fsHandlers)
  213. if err != nil {
  214. utils.SendErrorResponse(w, output)
  215. return
  216. }
  217. utils.SendTextResponse(w, output)
  218. } else {
  219. o, err := Mount(devID, safeMountPoint, mountingTool, fsHandlers)
  220. if err != nil {
  221. utils.SendErrorResponse(w, o)
  222. return
  223. }
  224. utils.SendTextResponse(w, o)
  225. }
  226. } else {
  227. utils.SendErrorResponse(w, "Platform not supported: "+runtime.GOOS)
  228. return
  229. }
  230. }
  231. /*
  232. Format Tool
  233. Manual translation from AOB's formatTool.php
  234. */
  235. func HandleFormat(w http.ResponseWriter, r *http.Request, fsHandlers []*fs.FileSystemHandler) {
  236. dev, err := utils.Mv(r, "dev", true)
  237. if err != nil {
  238. utils.SendErrorResponse(w, "dev not defined")
  239. return
  240. }
  241. format, err := utils.Mv(r, "format", true)
  242. if err != nil {
  243. utils.SendErrorResponse(w, "format not defined")
  244. return
  245. }
  246. if runtime.GOOS == "windows" {
  247. utils.SendErrorResponse(w, "This function is Linux Only")
  248. return
  249. }
  250. //Check if format is supported
  251. if !utils.StringInArray(supportedFormats, format) {
  252. utils.SendErrorResponse(w, "Format not supported")
  253. return
  254. }
  255. //Check if device is valid
  256. ok, devID := checkDeviceValid(dev)
  257. if !ok {
  258. utils.SendErrorResponse(w, "Device name is not valid")
  259. return
  260. }
  261. //Check if it is mounted. If yes, umount it
  262. mounted, err := checkDeviceMounted(devID)
  263. if err != nil {
  264. //Fail to check if disk mounted
  265. log.Println(err.Error())
  266. utils.SendErrorResponse(w, "Failed to check disk mount status")
  267. return
  268. }
  269. //This drive is still mounted. Unmount it
  270. if mounted {
  271. //Close all the fsHandler related to this disk
  272. mountpt, err := getDeviceMountPoint(devID)
  273. if err != nil {
  274. utils.SendErrorResponse(w, err.Error())
  275. return
  276. }
  277. log.Println("Unmounting " + mountpt + " for format")
  278. //Unmount the devices
  279. out, err := Unmount(mountpt, fsHandlers)
  280. if err != nil {
  281. utils.SendErrorResponse(w, out)
  282. return
  283. }
  284. }
  285. //Format the drive
  286. var cmd *exec.Cmd
  287. if format == "ntfs" {
  288. cmd = exec.Command("mkfs.ntfs", "-f", "/dev/"+devID)
  289. } else if format == "vfat" {
  290. cmd = exec.Command("mkfs.vfat", "/dev/"+devID)
  291. } else if format == "ext4" {
  292. cmd = exec.Command("mkfs.ext4", "-F", "/dev/"+devID)
  293. } else if format == "ext3" {
  294. utils.SendErrorResponse(w, "Format to ext3 is Work In Progress")
  295. } else if format == "btrfs" {
  296. utils.SendErrorResponse(w, "Format to btrfs is Work In Progress")
  297. } else {
  298. utils.SendErrorResponse(w, "Format tyoe not supported")
  299. }
  300. //Execute format comamnd
  301. log.Println("Formatting of " + "/dev/" + devID + " Started")
  302. output, err := cmd.CombinedOutput()
  303. if err != nil {
  304. log.Println("Format failed: " + string(output))
  305. utils.SendErrorResponse(w, string(output))
  306. return
  307. }
  308. //Reply ok
  309. log.Println(string(output))
  310. //Let the system to reload the disk
  311. time.Sleep(2 * time.Second)
  312. utils.SendOK(w)
  313. }
  314. func Mount(devID string, mountpt string, mountingTool string, fsHandlers []*fs.FileSystemHandler) (string, error) {
  315. //Loop each fsHandler. If exists one that fits and Closed, reopen it
  316. for _, fsh := range fsHandlers {
  317. if strings.Contains(filepath.ToSlash(fsh.Path), filepath.ToSlash(mountpt)) {
  318. //Re-open the file system database and set its flag to Open
  319. fsdbPath := filepath.ToSlash(filepath.Clean(fsh.Path)) + "/aofs.db"
  320. dbObject, err := db.NewDatabase(fsdbPath, false)
  321. if err != nil {
  322. continue
  323. }
  324. fsh.FilesystemDatabase = dbObject
  325. fsh.Closed = false
  326. }
  327. }
  328. log.Println("Executing Mount Command: ", "mount", "-t", mountingTool, "/dev/"+devID, mountpt)
  329. cmd := exec.Command("mount", "-t", mountingTool, "/dev/"+devID, mountpt)
  330. o, err := cmd.CombinedOutput()
  331. if err != nil {
  332. log.Println("Failed to mount "+devID, string(o))
  333. }
  334. return string(o), err
  335. }
  336. //Unmount a given mountpoint
  337. func Unmount(mountpt string, fsHandlers []*fs.FileSystemHandler) (string, error) {
  338. //Unmount the fsHandlers that related to this mountpt
  339. for _, fsh := range fsHandlers {
  340. if strings.Contains(filepath.ToSlash(fsh.Path), filepath.ToSlash(mountpt)) {
  341. //Close this file system handler
  342. fsh.FilesystemDatabase.Close()
  343. fsh.Closed = true
  344. }
  345. }
  346. log.Println("Executing Umount Command: ", "umount", mountpt)
  347. cmd := exec.Command("umount", mountpt)
  348. o, err := cmd.CombinedOutput()
  349. return string(o), err
  350. }
  351. //Return a list of mountable directory
  352. func HandleListMountPoints(w http.ResponseWriter, r *http.Request) {
  353. mp, _ := filepath.Glob("/media/*")
  354. js, _ := json.Marshal(mp)
  355. utils.SendJSONResponse(w, string(js))
  356. }
  357. //Check if the device is mounted
  358. func checkDeviceMounted(devname string) (bool, error) {
  359. cmd := exec.Command("bash", "-c", "lsblk -f -b --json | grep "+devname)
  360. output, err := cmd.CombinedOutput()
  361. if err != nil {
  362. return false, err
  363. }
  364. //Convert the json map to generic string interface map
  365. jsonMap := make(map[string]interface{})
  366. err = json.Unmarshal(output, &jsonMap)
  367. if err != nil {
  368. return false, err
  369. }
  370. if jsonMap["mountpoint"] != nil {
  371. return true, nil
  372. } else {
  373. return false, nil
  374. }
  375. }
  376. func getDeviceMountPoint(devname string) (string, error) {
  377. cmd := exec.Command("bash", "-c", "lsblk -f -b --json | grep "+devname)
  378. output, err := cmd.CombinedOutput()
  379. if err != nil {
  380. return "", errors.New("Device not mounted")
  381. }
  382. //Convert the json map to generic string interface map
  383. jsonMap := make(map[string]interface{})
  384. err = json.Unmarshal(output, &jsonMap)
  385. if err != nil {
  386. return "", errors.New("Pharse mountpoint error")
  387. }
  388. if jsonMap["mountpoint"] != nil {
  389. return jsonMap["mountpoint"].(string), nil
  390. } else {
  391. return "", errors.New("Unable to get mountpoint from lsblk")
  392. }
  393. }
  394. //Check device valid, only usable in linux
  395. func checkDeviceValid(devname string) (bool, string) {
  396. //Check if the device name is valid
  397. match, _ := regexp.MatchString("sd[a-z][1-9]", devname)
  398. if !match {
  399. return false, ""
  400. }
  401. //Extract the device name from string
  402. re := regexp.MustCompile(`sd[a-z][1-9]`)
  403. devID := re.FindString(devname)
  404. if !utils.FileExists("/dev/" + devID) {
  405. return false, ""
  406. }
  407. return true, devID
  408. }
  409. func HandlePlatform(w http.ResponseWriter, r *http.Request) {
  410. js, _ := json.Marshal(runtime.GOOS)
  411. utils.SendJSONResponse(w, string(js))
  412. }