startup.flags.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. package main
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "flag"
  6. "net/http"
  7. "strings"
  8. prout "imuslab.com/arozos/mod/prouter"
  9. "imuslab.com/arozos/mod/utils"
  10. )
  11. /*
  12. Startup Flags Manager
  13. This script is design to provide interface for editing the boot flags
  14. during the system is running.
  15. Changes are applied to the runtime and persisted to the system database,
  16. so they survive a restart. On boot, StartupFlagsRestore loads the stored
  17. values into the runtime, except for the ones given explicitly as start
  18. parameters: those win, and overwrite the stored value.
  19. */
  20. const bootFlagsTable = "bootflags"
  21. const bootFlagsKey = "config"
  22. // bootFlags is the persisted and editable subset of the startup parameters.
  23. // MaxUploadSize and MaxFileUploadBuff are in MB, FileIOBuffer in bytes.
  24. type bootFlags struct {
  25. Hostname string
  26. MaxUploadSize int
  27. MaxFileUploadBuff int
  28. FileIOBuffer int
  29. DisableIPResolver bool
  30. EnableHomePage bool
  31. EnableDirListing bool
  32. }
  33. // bootFlagNames maps each bootFlags field to the command line flag it mirrors
  34. var bootFlagNames = map[string]string{
  35. "Hostname": "hostname",
  36. "MaxUploadSize": "max_upload_size",
  37. "MaxFileUploadBuff": "upload_buf",
  38. "FileIOBuffer": "iobuf",
  39. "DisableIPResolver": "disable_ip_resolver",
  40. "EnableHomePage": "homepage",
  41. "EnableDirListing": "dir_list",
  42. }
  43. // currentBootFlags reads the boot flags from the runtime globals
  44. func currentBootFlags() bootFlags {
  45. return bootFlags{
  46. Hostname: *host_name,
  47. MaxUploadSize: int(max_upload_size >> 20),
  48. MaxFileUploadBuff: *upload_buf,
  49. FileIOBuffer: *file_opr_buff,
  50. DisableIPResolver: *disable_ip_resolve_services,
  51. EnableHomePage: *allow_homepage,
  52. EnableDirListing: *enable_dir_listing,
  53. }
  54. }
  55. // applyBootFlags writes the boot flags into the runtime globals
  56. func applyBootFlags(c bootFlags) {
  57. *host_name = c.Hostname
  58. *max_upload = c.MaxUploadSize
  59. max_upload_size = int64(c.MaxUploadSize) << 20
  60. *upload_buf = c.MaxFileUploadBuff
  61. *file_opr_buff = c.FileIOBuffer
  62. *disable_ip_resolve_services = c.DisableIPResolver
  63. *allow_homepage = c.EnableHomePage
  64. *enable_dir_listing = c.EnableDirListing
  65. }
  66. // validateBootFlags normalises and checks a config before it is applied
  67. func validateBootFlags(c *bootFlags) error {
  68. c.Hostname = strings.TrimSpace(c.Hostname)
  69. if c.Hostname == "" {
  70. return errors.New("host name cannot be empty")
  71. }
  72. if c.MaxUploadSize <= 0 {
  73. return errors.New("max upload size must be larger than zero")
  74. }
  75. if c.MaxFileUploadBuff <= 0 {
  76. return errors.New("file upload buffer must be larger than zero")
  77. }
  78. if c.FileIOBuffer <= 0 {
  79. return errors.New("file IO buffer must be larger than zero")
  80. }
  81. return nil
  82. }
  83. // explicitStartFlags returns the names of the flags given on the command line
  84. func explicitStartFlags() map[string]bool {
  85. set := map[string]bool{}
  86. flag.Visit(func(f *flag.Flag) {
  87. set[f.Name] = true
  88. })
  89. return set
  90. }
  91. /*
  92. mergeBootFlags decides the boot flags to run with. Fields whose flag was given
  93. explicitly keep the runtime value, other fields take the stored value when one
  94. exists (and is valid), and fall back to the runtime default otherwise. stored
  95. is kept as raw fields so a config saved by an older version, missing some
  96. fields, only restores the ones it has.
  97. */
  98. func mergeBootFlags(runtime bootFlags, stored map[string]json.RawMessage, explicit map[string]bool) bootFlags {
  99. merged := runtime
  100. restore := func(field string, target interface{}) {
  101. raw, ok := stored[field]
  102. if !ok || explicit[bootFlagNames[field]] {
  103. return
  104. }
  105. json.Unmarshal(raw, target)
  106. }
  107. restore("Hostname", &merged.Hostname)
  108. restore("MaxUploadSize", &merged.MaxUploadSize)
  109. restore("MaxFileUploadBuff", &merged.MaxFileUploadBuff)
  110. restore("FileIOBuffer", &merged.FileIOBuffer)
  111. restore("DisableIPResolver", &merged.DisableIPResolver)
  112. restore("EnableHomePage", &merged.EnableHomePage)
  113. restore("EnableDirListing", &merged.EnableDirListing)
  114. //A damaged stored value must not stop the system from booting
  115. if strings.TrimSpace(merged.Hostname) == "" {
  116. merged.Hostname = runtime.Hostname
  117. }
  118. if merged.MaxUploadSize <= 0 {
  119. merged.MaxUploadSize = runtime.MaxUploadSize
  120. }
  121. if merged.MaxFileUploadBuff <= 0 {
  122. merged.MaxFileUploadBuff = runtime.MaxFileUploadBuff
  123. }
  124. if merged.FileIOBuffer <= 0 {
  125. merged.FileIOBuffer = runtime.FileIOBuffer
  126. }
  127. return merged
  128. }
  129. /*
  130. StartupFlagsRestore loads the persisted boot flags into the runtime. It must
  131. run right after the system database opens and before any service reads these
  132. flags (e.g. the host name is captured by several modules at init).
  133. */
  134. func StartupFlagsRestore() {
  135. sysdb.NewTable(bootFlagsTable)
  136. stored := map[string]json.RawMessage{}
  137. if sysdb.KeyExists(bootFlagsTable, bootFlagsKey) {
  138. if err := sysdb.Read(bootFlagsTable, bootFlagsKey, &stored); err != nil {
  139. systemWideLogger.PrintAndLog("System", "Unable to read stored startup parameters, using defaults", err)
  140. stored = map[string]json.RawMessage{}
  141. }
  142. }
  143. merged := mergeBootFlags(currentBootFlags(), stored, explicitStartFlags())
  144. applyBootFlags(merged)
  145. //Write back so the start flags given this boot overwrite the stored values
  146. if err := sysdb.Write(bootFlagsTable, bootFlagsKey, merged); err != nil {
  147. systemWideLogger.PrintAndLog("System", "Unable to persist startup parameters", err)
  148. }
  149. }
  150. func StartupFlagsInit() {
  151. //Create a admin permission router for handling requests
  152. //Register a boot flag modifier
  153. registerSetting(settingModule{
  154. Name: "Runtime",
  155. Desc: "Change startup paramter in runtime",
  156. IconPath: "SystemAO/info/img/runtime.png",
  157. Group: "Info",
  158. StartDir: "SystemAO/boot/bootflags.html",
  159. RequireAdmin: true,
  160. })
  161. adminRouter := prout.NewModuleRouter(prout.RouterOption{
  162. AdminOnly: true,
  163. UserHandler: userHandler,
  164. DeniedHandler: func(w http.ResponseWriter, r *http.Request) {
  165. utils.SendErrorResponse(w, "Permission Denied")
  166. },
  167. })
  168. adminRouter.HandleFunc("/system/bootflags", handleBootFlagsFunction)
  169. }
  170. func handleBootFlagsFunction(w http.ResponseWriter, r *http.Request) {
  171. opr, _ := utils.PostPara(r, "opr")
  172. if opr == "" {
  173. //List the current boot flags, together with the fields that were
  174. //given as start parameters this boot (they override the stored value)
  175. explicit := explicitStartFlags()
  176. fromStartFlag := []string{}
  177. for field, name := range bootFlagNames {
  178. if explicit[name] {
  179. fromStartFlag = append(fromStartFlag, field)
  180. }
  181. }
  182. js, _ := json.Marshal(struct {
  183. bootFlags
  184. FromStartFlag []string
  185. }{currentBootFlags(), fromStartFlag})
  186. utils.SendJSONResponse(w, string(js))
  187. } else if opr == "set" {
  188. //Set and update the boot flags
  189. newSettings, err := utils.PostPara(r, "value")
  190. if err != nil {
  191. utils.SendErrorResponse(w, "Invalid new seting value")
  192. return
  193. }
  194. //Fields missing from the request keep their current value
  195. newConfig := currentBootFlags()
  196. err = json.Unmarshal([]byte(newSettings), &newConfig)
  197. if err != nil {
  198. utils.SendErrorResponse(w, err.Error())
  199. return
  200. }
  201. if err = validateBootFlags(&newConfig); err != nil {
  202. utils.SendErrorResponse(w, err.Error())
  203. return
  204. }
  205. //Update the current global flags and persist them for the next boot
  206. systemWideLogger.PrintAndLog("System", "Updating boot flag to:"+newSettings, nil)
  207. applyBootFlags(newConfig)
  208. if err = sysdb.Write(bootFlagsTable, bootFlagsKey, newConfig); err != nil {
  209. systemWideLogger.PrintAndLog("System", "Unable to persist startup parameters", err)
  210. utils.SendErrorResponse(w, "Applied to runtime but failed to save: "+err.Error())
  211. return
  212. }
  213. utils.SendOK(w)
  214. } else {
  215. utils.SendErrorResponse(w, "Unknown operation")
  216. }
  217. }