api_quota.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. package httpd
  2. import (
  3. "errors"
  4. "net/http"
  5. "github.com/go-chi/render"
  6. "github.com/drakkan/sftpgo/common"
  7. "github.com/drakkan/sftpgo/dataprovider"
  8. "github.com/drakkan/sftpgo/logger"
  9. "github.com/drakkan/sftpgo/vfs"
  10. )
  11. const (
  12. quotaUpdateModeAdd = "add"
  13. quotaUpdateModeReset = "reset"
  14. )
  15. func getQuotaScans(w http.ResponseWriter, r *http.Request) {
  16. render.JSON(w, r, common.QuotaScans.GetUsersQuotaScans())
  17. }
  18. func getVFolderQuotaScans(w http.ResponseWriter, r *http.Request) {
  19. render.JSON(w, r, common.QuotaScans.GetVFoldersQuotaScans())
  20. }
  21. func updateUserQuotaUsage(w http.ResponseWriter, r *http.Request) {
  22. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  23. var u dataprovider.User
  24. err := render.DecodeJSON(r.Body, &u)
  25. if err != nil {
  26. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  27. return
  28. }
  29. if u.UsedQuotaFiles < 0 || u.UsedQuotaSize < 0 {
  30. sendAPIResponse(w, r, errors.New("Invalid used quota parameters, negative values are not allowed"),
  31. "", http.StatusBadRequest)
  32. return
  33. }
  34. mode, err := getQuotaUpdateMode(r)
  35. if err != nil {
  36. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  37. return
  38. }
  39. user, err := dataprovider.UserExists(u.Username)
  40. if err != nil {
  41. sendAPIResponse(w, r, err, "", getRespStatus(err))
  42. return
  43. }
  44. if mode == quotaUpdateModeAdd && !user.HasQuotaRestrictions() && dataprovider.GetQuotaTracking() == 2 {
  45. sendAPIResponse(w, r, errors.New("this user has no quota restrictions, only reset mode is supported"),
  46. "", http.StatusBadRequest)
  47. return
  48. }
  49. if !common.QuotaScans.AddUserQuotaScan(user.Username) {
  50. sendAPIResponse(w, r, err, "A quota scan is in progress for this user", http.StatusConflict)
  51. return
  52. }
  53. defer common.QuotaScans.RemoveUserQuotaScan(user.Username)
  54. err = dataprovider.UpdateUserQuota(user, u.UsedQuotaFiles, u.UsedQuotaSize, mode == quotaUpdateModeReset)
  55. if err != nil {
  56. sendAPIResponse(w, r, err, "", getRespStatus(err))
  57. } else {
  58. sendAPIResponse(w, r, err, "Quota updated", http.StatusOK)
  59. }
  60. }
  61. func updateVFolderQuotaUsage(w http.ResponseWriter, r *http.Request) {
  62. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  63. var f vfs.BaseVirtualFolder
  64. err := render.DecodeJSON(r.Body, &f)
  65. if err != nil {
  66. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  67. return
  68. }
  69. if f.UsedQuotaFiles < 0 || f.UsedQuotaSize < 0 {
  70. sendAPIResponse(w, r, errors.New("Invalid used quota parameters, negative values are not allowed"),
  71. "", http.StatusBadRequest)
  72. return
  73. }
  74. mode, err := getQuotaUpdateMode(r)
  75. if err != nil {
  76. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  77. return
  78. }
  79. folder, err := dataprovider.GetFolderByPath(f.MappedPath)
  80. if err != nil {
  81. sendAPIResponse(w, r, err, "", getRespStatus(err))
  82. return
  83. }
  84. if !common.QuotaScans.AddVFolderQuotaScan(folder.MappedPath) {
  85. sendAPIResponse(w, r, err, "A quota scan is in progress for this folder", http.StatusConflict)
  86. return
  87. }
  88. defer common.QuotaScans.RemoveVFolderQuotaScan(folder.MappedPath)
  89. err = dataprovider.UpdateVirtualFolderQuota(folder, f.UsedQuotaFiles, f.UsedQuotaSize, mode == quotaUpdateModeReset)
  90. if err != nil {
  91. sendAPIResponse(w, r, err, "", getRespStatus(err))
  92. } else {
  93. sendAPIResponse(w, r, err, "Quota updated", http.StatusOK)
  94. }
  95. }
  96. func startQuotaScan(w http.ResponseWriter, r *http.Request) {
  97. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  98. if dataprovider.GetQuotaTracking() == 0 {
  99. sendAPIResponse(w, r, nil, "Quota tracking is disabled!", http.StatusForbidden)
  100. return
  101. }
  102. var u dataprovider.User
  103. err := render.DecodeJSON(r.Body, &u)
  104. if err != nil {
  105. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  106. return
  107. }
  108. user, err := dataprovider.UserExists(u.Username)
  109. if err != nil {
  110. sendAPIResponse(w, r, err, "", getRespStatus(err))
  111. return
  112. }
  113. if common.QuotaScans.AddUserQuotaScan(user.Username) {
  114. go doQuotaScan(user) //nolint:errcheck
  115. sendAPIResponse(w, r, err, "Scan started", http.StatusAccepted)
  116. } else {
  117. sendAPIResponse(w, r, err, "Another scan is already in progress", http.StatusConflict)
  118. }
  119. }
  120. func startVFolderQuotaScan(w http.ResponseWriter, r *http.Request) {
  121. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  122. if dataprovider.GetQuotaTracking() == 0 {
  123. sendAPIResponse(w, r, nil, "Quota tracking is disabled!", http.StatusForbidden)
  124. return
  125. }
  126. var f vfs.BaseVirtualFolder
  127. err := render.DecodeJSON(r.Body, &f)
  128. if err != nil {
  129. sendAPIResponse(w, r, err, "", http.StatusBadRequest)
  130. return
  131. }
  132. folder, err := dataprovider.GetFolderByPath(f.MappedPath)
  133. if err != nil {
  134. sendAPIResponse(w, r, err, "", getRespStatus(err))
  135. return
  136. }
  137. if common.QuotaScans.AddVFolderQuotaScan(folder.MappedPath) {
  138. go doFolderQuotaScan(folder) //nolint:errcheck
  139. sendAPIResponse(w, r, err, "Scan started", http.StatusAccepted)
  140. } else {
  141. sendAPIResponse(w, r, err, "Another scan is already in progress", http.StatusConflict)
  142. }
  143. }
  144. func doQuotaScan(user dataprovider.User) error {
  145. defer common.QuotaScans.RemoveUserQuotaScan(user.Username)
  146. fs, err := user.GetFilesystem("")
  147. if err != nil {
  148. logger.Warn(logSender, "", "unable scan quota for user %#v error creating filesystem: %v", user.Username, err)
  149. return err
  150. }
  151. numFiles, size, err := fs.ScanRootDirContents()
  152. if err != nil {
  153. logger.Warn(logSender, "", "error scanning user home dir %#v: %v", user.Username, err)
  154. return err
  155. }
  156. err = dataprovider.UpdateUserQuota(user, numFiles, size, true)
  157. logger.Debug(logSender, "", "user home dir scanned, user: %#v, error: %v", user.Username, err)
  158. return err
  159. }
  160. func doFolderQuotaScan(folder vfs.BaseVirtualFolder) error {
  161. defer common.QuotaScans.RemoveVFolderQuotaScan(folder.MappedPath)
  162. fs := vfs.NewOsFs("", "", nil).(vfs.OsFs)
  163. numFiles, size, err := fs.GetDirSize(folder.MappedPath)
  164. if err != nil {
  165. logger.Warn(logSender, "", "error scanning folder %#v: %v", folder.MappedPath, err)
  166. return err
  167. }
  168. err = dataprovider.UpdateVirtualFolderQuota(folder, numFiles, size, true)
  169. logger.Debug(logSender, "", "virtual folder %#v scanned, error: %v", folder.MappedPath, err)
  170. return err
  171. }
  172. func getQuotaUpdateMode(r *http.Request) (string, error) {
  173. mode := quotaUpdateModeReset
  174. if _, ok := r.URL.Query()["mode"]; ok {
  175. mode = r.URL.Query().Get("mode")
  176. if mode != quotaUpdateModeReset && mode != quotaUpdateModeAdd {
  177. return "", errors.New("Invalid mode")
  178. }
  179. }
  180. return mode, nil
  181. }