diskmg.go 12 KB

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