diskmg.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  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", true)
  236. if err != nil {
  237. sendErrorResponse(w, "dev not defined")
  238. return
  239. }
  240. format, err := mv(r, "format", true)
  241. if err != nil {
  242. sendErrorResponse(w, "format not defined")
  243. return
  244. }
  245. if runtime.GOOS == "windows" {
  246. sendErrorResponse(w, "This function is Linux Only")
  247. return
  248. }
  249. //Check if format is supported
  250. if !inArray(supportedFormats, format) {
  251. sendErrorResponse(w, "Format not supported")
  252. return
  253. }
  254. //Check if device is valid
  255. ok, devID := checkDeviceValid(dev)
  256. if !ok {
  257. sendErrorResponse(w, "Device name is not valid")
  258. return
  259. }
  260. //Check if it is mounted. If yes, umount it
  261. mounted, err := checkDeviceMounted(devID)
  262. if err != nil {
  263. //Fail to check if disk mounted
  264. log.Println(err.Error())
  265. sendErrorResponse(w, "Failed to check disk mount status")
  266. return
  267. }
  268. //This drive is still mounted. Unmount it
  269. if mounted {
  270. //Close all the fsHandler related to this disk
  271. mountpt, err := getDeviceMountPoint(devID)
  272. if err != nil {
  273. sendErrorResponse(w, err.Error())
  274. return
  275. }
  276. log.Println("Unmounting " + mountpt + " for format")
  277. //Unmount the devices
  278. out, err := Unmount(mountpt, fsHandlers)
  279. if err != nil {
  280. sendErrorResponse(w, out)
  281. return
  282. }
  283. }
  284. //Format the drive
  285. var cmd *exec.Cmd
  286. if format == "ntfs" {
  287. cmd = exec.Command("mkfs.ntfs", "-f", "/dev/"+devID)
  288. } else if format == "vfat" {
  289. cmd = exec.Command("mkfs.vfat", "/dev/"+devID)
  290. } else if format == "ext4" {
  291. cmd = exec.Command("mkfs.ext4", "-F", "/dev/"+devID)
  292. } else if format == "ext3" {
  293. sendErrorResponse(w, "Format to ext3 is Work In Progress")
  294. } else if format == "btrfs" {
  295. sendErrorResponse(w, "Format to btrfs is Work In Progress")
  296. } else {
  297. sendErrorResponse(w, "Format tyoe not supported")
  298. }
  299. //Execute format comamnd
  300. log.Println("Formatting of " + "/dev/" + devID + " Started")
  301. output, err := cmd.CombinedOutput()
  302. if err != nil {
  303. log.Println("Format failed: " + string(output))
  304. sendErrorResponse(w, string(output))
  305. return
  306. }
  307. //Reply ok
  308. log.Println(string(output))
  309. //Let the system to reload the disk
  310. time.Sleep(2 * time.Second)
  311. sendOK(w)
  312. }
  313. func Mount(devID string, mountpt string, mountingTool string, fsHandlers []*fs.FileSystemHandler) (string, error) {
  314. //Loop each fsHandler. If exists one that fits and Closed, reopen it
  315. for _, fsh := range fsHandlers {
  316. if strings.Contains(filepath.ToSlash(fsh.Path), filepath.ToSlash(mountpt)) {
  317. //Re-open the file system database and set its flag to Open
  318. fsdbPath := filepath.ToSlash(filepath.Clean(fsh.Path)) + "/aofs.db"
  319. dbObject, err := db.NewDatabase(fsdbPath, false)
  320. if err != nil {
  321. continue
  322. }
  323. fsh.FilesystemDatabase = dbObject
  324. fsh.Closed = false
  325. }
  326. }
  327. log.Println("Executing Mount Command: ", "mount", "-t", mountingTool, "/dev/"+devID, mountpt)
  328. cmd := exec.Command("mount", "-t", mountingTool, "/dev/"+devID, mountpt)
  329. o, err := cmd.CombinedOutput()
  330. if err != nil {
  331. log.Println("Failed to mount "+devID, string(o))
  332. }
  333. return string(o), err
  334. }
  335. //Unmount a given mountpoint
  336. func Unmount(mountpt string, fsHandlers []*fs.FileSystemHandler) (string, error) {
  337. //Unmount the fsHandlers that related to this mountpt
  338. for _, fsh := range fsHandlers {
  339. if strings.Contains(filepath.ToSlash(fsh.Path), filepath.ToSlash(mountpt)) {
  340. //Close this file system handler
  341. fsh.FilesystemDatabase.Close()
  342. fsh.Closed = true
  343. }
  344. }
  345. log.Println("Executing Umount Command: ", "umount", mountpt)
  346. cmd := exec.Command("umount", mountpt)
  347. o, err := cmd.CombinedOutput()
  348. return string(o), err
  349. }
  350. //Return a list of mountable directory
  351. func HandleListMountPoints(w http.ResponseWriter, r *http.Request) {
  352. mp, _ := filepath.Glob("/media/*")
  353. js, _ := json.Marshal(mp)
  354. sendJSONResponse(w, string(js))
  355. }
  356. //Check if the device is mounted
  357. func checkDeviceMounted(devname string) (bool, error) {
  358. cmd := exec.Command("bash", "-c", "lsblk -f -b --json | grep "+devname)
  359. output, err := cmd.CombinedOutput()
  360. if err != nil {
  361. return false, err
  362. }
  363. //Convert the json map to generic string interface map
  364. jsonMap := make(map[string]interface{})
  365. err = json.Unmarshal(output, &jsonMap)
  366. if err != nil {
  367. return false, err
  368. }
  369. if jsonMap["mountpoint"] != nil {
  370. return true, nil
  371. } else {
  372. return false, nil
  373. }
  374. }
  375. func getDeviceMountPoint(devname string) (string, error) {
  376. cmd := exec.Command("bash", "-c", "lsblk -f -b --json | grep "+devname)
  377. output, err := cmd.CombinedOutput()
  378. if err != nil {
  379. return "", errors.New("Device not mounted")
  380. }
  381. //Convert the json map to generic string interface map
  382. jsonMap := make(map[string]interface{})
  383. err = json.Unmarshal(output, &jsonMap)
  384. if err != nil {
  385. return "", errors.New("Pharse mountpoint error")
  386. }
  387. if jsonMap["mountpoint"] != nil {
  388. return jsonMap["mountpoint"].(string), nil
  389. } else {
  390. return "", errors.New("Unable to get mountpoint from lsblk")
  391. }
  392. }
  393. //Check device valid, only usable in linux
  394. func checkDeviceValid(devname string) (bool, string) {
  395. //Check if the device name is valid
  396. match, _ := regexp.MatchString("sd[a-z][1-9]", devname)
  397. if !match {
  398. return false, ""
  399. }
  400. //Extract the device name from string
  401. re := regexp.MustCompile(`sd[a-z][1-9]`)
  402. devID := re.FindString(devname)
  403. if !fileExists("/dev/" + devID) {
  404. return false, ""
  405. }
  406. return true, devID
  407. }
  408. func HandlePlatform(w http.ResponseWriter, r *http.Request) {
  409. js, _ := json.Marshal(runtime.GOOS)
  410. sendJSONResponse(w, string(js))
  411. }