You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
6.0 KiB

  1. /*
  2. * Minio Cloud Storage, (C) 2015 Minio, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package cmd
  17. import (
  18. "encoding/base64"
  19. "encoding/xml"
  20. "fmt"
  21. "io"
  22. "net"
  23. "net/http"
  24. "os"
  25. "path/filepath"
  26. "runtime"
  27. "strings"
  28. "encoding/json"
  29. "github.com/pkg/profile"
  30. )
  31. // make a copy of http.Header
  32. func cloneHeader(h http.Header) http.Header {
  33. h2 := make(http.Header, len(h))
  34. for k, vv := range h {
  35. vv2 := make([]string, len(vv))
  36. copy(vv2, vv)
  37. h2[k] = vv2
  38. }
  39. return h2
  40. }
  41. // checkDuplicates - function to validate if there are duplicates in a slice of strings.
  42. func checkDuplicates(list []string) error {
  43. // Empty lists are not allowed.
  44. if len(list) == 0 {
  45. return errInvalidArgument
  46. }
  47. // Empty keys are not allowed.
  48. for _, key := range list {
  49. if key == "" {
  50. return errInvalidArgument
  51. }
  52. }
  53. listMaps := make(map[string]int)
  54. // Navigate through each configs and count the entries.
  55. for _, key := range list {
  56. listMaps[key]++
  57. }
  58. // Validate if there are any duplicate counts.
  59. for key, count := range listMaps {
  60. if count != 1 {
  61. return fmt.Errorf("Duplicate key: \"%s\" found of count: \"%d\"", key, count)
  62. }
  63. }
  64. // No duplicates.
  65. return nil
  66. }
  67. // splits network path into its components Address and Path.
  68. func splitNetPath(networkPath string) (netAddr, netPath string, err error) {
  69. if runtime.GOOS == "windows" {
  70. if volumeName := filepath.VolumeName(networkPath); volumeName != "" {
  71. return "", networkPath, nil
  72. }
  73. }
  74. networkParts := strings.SplitN(networkPath, ":", 2)
  75. switch {
  76. case len(networkParts) == 1:
  77. return "", networkPath, nil
  78. case networkParts[1] == "":
  79. return "", "", &net.AddrError{Err: "Missing path in network path", Addr: networkPath}
  80. case networkParts[0] == "":
  81. return "", "", &net.AddrError{Err: "Missing address in network path", Addr: networkPath}
  82. case !filepath.IsAbs(networkParts[1]):
  83. return "", "", &net.AddrError{Err: "Network path should be absolute", Addr: networkPath}
  84. }
  85. return networkParts[0], networkParts[1], nil
  86. }
  87. // Find local node through the command line arguments. Returns in
  88. // `host:port` format.
  89. func getLocalAddress(srvCmdConfig serverCmdConfig) string {
  90. if !srvCmdConfig.isDistXL {
  91. return fmt.Sprintf(":%d", globalMinioPort)
  92. }
  93. for _, export := range srvCmdConfig.disks {
  94. // Validates if remote disk is local.
  95. if isLocalStorage(export) {
  96. var host string
  97. if idx := strings.LastIndex(export, ":"); idx != -1 {
  98. host = export[:idx]
  99. }
  100. return fmt.Sprintf("%s:%d", host, globalMinioPort)
  101. }
  102. }
  103. return ""
  104. }
  105. // xmlDecoder provide decoded value in xml.
  106. func xmlDecoder(body io.Reader, v interface{}, size int64) error {
  107. var lbody io.Reader
  108. if size > 0 {
  109. lbody = io.LimitReader(body, size)
  110. } else {
  111. lbody = body
  112. }
  113. d := xml.NewDecoder(lbody)
  114. return d.Decode(v)
  115. }
  116. // checkValidMD5 - verify if valid md5, returns md5 in bytes.
  117. func checkValidMD5(md5 string) ([]byte, error) {
  118. return base64.StdEncoding.DecodeString(strings.TrimSpace(md5))
  119. }
  120. /// http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html
  121. const (
  122. // maximum object size per PUT request is 5GiB
  123. maxObjectSize = 1024 * 1024 * 1024 * 5
  124. // minimum Part size for multipart upload is 5MB
  125. minPartSize = 1024 * 1024 * 5
  126. // maximum Part ID for multipart upload is 10000 (Acceptable values range from 1 to 10000 inclusive)
  127. maxPartID = 10000
  128. )
  129. // isMaxObjectSize - verify if max object size
  130. func isMaxObjectSize(size int64) bool {
  131. return size > maxObjectSize
  132. }
  133. // Check if part size is more than or equal to minimum allowed size.
  134. func isMinAllowedPartSize(size int64) bool {
  135. return size >= minPartSize
  136. }
  137. // isMaxPartNumber - Check if part ID is greater than the maximum allowed ID.
  138. func isMaxPartID(partID int) bool {
  139. return partID > maxPartID
  140. }
  141. func contains(stringList []string, element string) bool {
  142. for _, e := range stringList {
  143. if e == element {
  144. return true
  145. }
  146. }
  147. return false
  148. }
  149. // urlPathSplit - split url path into bucket and object components.
  150. func urlPathSplit(urlPath string) (bucketName, prefixName string) {
  151. if urlPath == "" {
  152. return urlPath, ""
  153. }
  154. urlPath = strings.TrimPrefix(urlPath, "/")
  155. i := strings.Index(urlPath, "/")
  156. if i != -1 {
  157. return urlPath[:i], urlPath[i+1:]
  158. }
  159. return urlPath, ""
  160. }
  161. // Starts a profiler returns nil if profiler is not enabled, caller needs to handle this.
  162. func startProfiler(profiler string) interface {
  163. Stop()
  164. } {
  165. // Set ``MINIO_PROFILE_DIR`` to the directory where profiling information should be persisted
  166. profileDir := os.Getenv("MINIO_PROFILE_DIR")
  167. // Enable profiler if ``MINIO_PROFILER`` is set. Supported options are [cpu, mem, block].
  168. switch profiler {
  169. case "cpu":
  170. return profile.Start(profile.CPUProfile, profile.NoShutdownHook, profile.ProfilePath(profileDir))
  171. case "mem":
  172. return profile.Start(profile.MemProfile, profile.NoShutdownHook, profile.ProfilePath(profileDir))
  173. case "block":
  174. return profile.Start(profile.BlockProfile, profile.NoShutdownHook, profile.ProfilePath(profileDir))
  175. default:
  176. return nil
  177. }
  178. }
  179. // Global profiler to be used by service go-routine.
  180. var globalProfiler interface {
  181. Stop()
  182. }
  183. // dump the request into a string in JSON format.
  184. func dumpRequest(r *http.Request) string {
  185. header := cloneHeader(r.Header)
  186. header.Set("Host", r.Host)
  187. req := struct {
  188. Method string `json:"method"`
  189. Path string `json:"path"`
  190. Query string `json:"query"`
  191. Header http.Header `json:"header"`
  192. }{r.Method, r.URL.Path, r.URL.RawQuery, header}
  193. jsonBytes, err := json.Marshal(req)
  194. if err != nil {
  195. return "<error dumping request>"
  196. }
  197. return string(jsonBytes)
  198. }