diff --git a/pkg/tasker/commands.go b/pkg/tasker/commands.go index 6d752ba2a..032bd566c 100644 --- a/pkg/tasker/commands.go +++ b/pkg/tasker/commands.go @@ -23,7 +23,8 @@ type Command uint8 const ( // CmdNOOP does nothing. It is a default placeholder. Uninitialized variable of this type will point to NOOP command by default. CmdNOOP Command = iota - // CmdSignalEnd gracefully ends current task. Never ending tasks (loop over) or Batched jobs will not take the next iteration, but may finish the current state to completion. + // CmdSignalEnd gracefully ends current task. Never ending tasks (loop over) or Batched jobs will not take the next iteration, + // but may finish the current state to completion. CmdSignalEnd // CmdSignalAbort ends the current task at hand immediately. It may still cleanup dangling issues quickly. CmdSignalAbort @@ -37,6 +38,7 @@ const ( CmdPriorityMedium // CmdPriorityHigh is optimized for speed. This option is ideal for short lived tasks (like meta-data related) that are latency sensitive. Use this option wisely. CmdPriorityHigh - // CmdPrioritySuper is an exclusive priority. All tasks with priority lower than Super (including High) are paused temporarily until this task completes. Anytime you consider using this priority level, please seek for approval. + // CmdPrioritySuper is an exclusive priority. All tasks with priority lower than Super (including High) are paused + // temporarily until this task completes. Anytime you consider using this priority level, please seek for approval. CmdPrioritySuper ) diff --git a/pkg/tasker/task.go b/pkg/tasker/task.go index bddf06dba..a3aa7ea63 100644 --- a/pkg/tasker/task.go +++ b/pkg/tasker/task.go @@ -21,20 +21,18 @@ import ( "sync" ) -/* NOTE: -Task is a private entity. It is created and managed by TaskCtl -entirely. Only TaskCtl and Handle objects are exposed outside. -*/ +// NOTE: Task is a private entity. It is created and managed by TaskCtl +// entirely. Only TaskCtl and Handle objects are exposed outside. -/* taskRef is a unique reference ID to a task. It is assigned by the -TaskCtl during the creation of a task. All tasfRef variables are -named "this". */ +// taskRef is a unique reference ID to a task. It is assigned by the +// TaskCtl during the creation of a task. All tasfRef variables are +// named "this". type taskRef *list.Element -/* Task is an abstract concept built on top of Go routines and -channels. Tasks themselves are expected to co-operate and comply with -the TaskCtl commands. -*/ +// Task is an abstract concept built on top of Go routines and +// channels. Tasks themselves are expected to co-operate and comply with +// the TaskCtl commands. + type task struct { mutex *sync.Mutex @@ -46,14 +44,13 @@ type task struct { closeCh chan taskRef // Channel to notify the TaskCtl about ending this task. } -/* NewTask creates a new task structure and returns a handle to -it. Only the task controller has access to the task structure. The -caller routine only receives a handle to its task structure. Task -handle is like a reference to task self. Caller is expected to listen -for commands from the task controller and comply with it -co-operatively. -this: Task reference is unique identifier assigned by the TaskCtl. -name: Free form name of the task. Eg. "Late Night Disk Scrubber". */ +// NewTask creates a new task structure and returns a handle to +// it. Only the task controller has access to the task structure. The +// caller routine only receives a handle to its task structure. Task +// handle is like a reference to task self. Caller is expected to listen +// for commands from the task controller and comply with it co-operatively. +// this: Task reference is unique identifier assigned by the TaskCtl. +// name: Free form name of the task. Eg. "Late Night Disk Scrubber". func newTask(name string) task { return task{ // this: Is set by the TaskCtl's NewTask function. diff --git a/pkg/tasker/taskctl.go b/pkg/tasker/taskctl.go index 8ac0cb252..ab63f7b2e 100644 --- a/pkg/tasker/taskctl.go +++ b/pkg/tasker/taskctl.go @@ -29,7 +29,8 @@ type TaskCtl struct { tasks *list.List } -// New creates a new TaskCtl to create and control a collection of tasks. Single application can create multiple task controllers to manage different set of tasks separately. +// New creates a new TaskCtl to create and control a collection of tasks. +// Single application can create multiple task controllers to manage different set of tasks separately. func New(name string) *TaskCtl { return &TaskCtl{ mutex: &sync.Mutex{}, @@ -37,7 +38,10 @@ func New(name string) *TaskCtl { } } -// NewTask creates a new task structure and returns a handle to it. Only the task controller has access to the task structure. The caller routine only receives a handle to its task structure. Task handle is like a reference to task self. Caller is expected to listen for commands from the task controller and comply with it co-operatively. +// NewTask creates a new task structure and returns a handle to it. Only the task controller +// has access to the task structure. The caller routine only receives a handle to its task structure. +// Task handle is like a reference to task self. Caller is expected to listen for commands from +// the task controller and comply with it co-operatively. func (tc *TaskCtl) NewTask(name string) Handle { tc.mutex.Lock() defer tc.mutex.Unlock() @@ -45,7 +49,7 @@ func (tc *TaskCtl) NewTask(name string) Handle { // Create a new task. tsk := newTask(name) - //Register this task in the TaskCtl's tasklist and save the reference. + // Register this task in the TaskCtl's tasklist and save the reference. tsk.this = tc.tasks.PushBack(tsk) // Free task from the tasklist upon close call. @@ -79,7 +83,10 @@ func (tc *TaskCtl) Shutdown() { wg.Add(1) thisTask := e.Value.(task) // Make a local copy for go routine. // End tasks in background. Flow of events from here is as follows: thisTask.handle.Close() -> tc.NewTask() -> this.task.close(). - go func() { thisTask.getHandle().Close(); wg.Done() }() + go func() { + thisTask.getHandle().Close() + wg.Done() + }() } wg.Wait() // Wait for all tasks to end gracefully. @@ -105,10 +112,10 @@ func (tc *TaskCtl) Suspend() bool { locTask := e.Value.(task) // Make a local copy for go routine. locI := i // local i // Suspend a task in background. - go func() { + go func(locI int) { defer wg.Done() statusAll[locI] = locTask.command(CmdSignalSuspend) - }() + }(locI) i++ } @@ -139,10 +146,10 @@ func (tc *TaskCtl) Resume() bool { locTask := e.Value.(task) // Make a local copy for go routine. locI := i // local i // Resume a task in background. - go func() { + go func(locI int) { defer wg.Done() statusAll[locI] = locTask.command(CmdSignalResume) - }() + }(locI) i++ } wg.Wait() // Wait for all tasks to resume. diff --git a/server-api-bucket-handlers.go b/server-api-bucket-handlers.go index bd40c7e6d..d2ab6f21e 100644 --- a/server-api-bucket-handlers.go +++ b/server-api-bucket-handlers.go @@ -403,8 +403,6 @@ func (api API) PutBucketACLHandler(w http.ResponseWriter, req *http.Request) { if err != nil { errorIf(err.Trace(), "PutBucketACL failed.", nil) switch err.ToGoError().(type) { - case signv4.DoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, req.URL.Path) case donut.BucketNameInvalid: writeErrorResponse(w, req, InvalidBucketName, req.URL.Path) case donut.BucketNotFound: @@ -417,6 +415,47 @@ func (api API) PutBucketACLHandler(w http.ResponseWriter, req *http.Request) { writeSuccessResponse(w) } +// GetBucketACLHandler - GET ACL on a Bucket +// ---------- +// This operation uses acl subresource to the return the ``acl`` +// of a bucket. One must have permission to access the bucket to +// know its ``acl``. This operation willl return response of 404 +// if bucket not found and 403 for invalid credentials. +func (api API) GetBucketACLHandler(w http.ResponseWriter, req *http.Request) { + // Ticket master block + { + op := APIOperation{} + op.ProceedCh = make(chan struct{}) + api.OP <- op + // block until Ticket master gives us a go + <-op.ProceedCh + } + + vars := mux.Vars(req) + bucket := vars["bucket"] + + bucketMetadata, err := api.Donut.GetBucketMetadata(bucket) + if err != nil { + errorIf(err.Trace(), "GetBucketMetadata failed.", nil) + switch err.ToGoError().(type) { + case donut.BucketNotFound: + writeErrorResponse(w, req, NoSuchBucket, req.URL.Path) + case donut.BucketNameInvalid: + writeErrorResponse(w, req, InvalidBucketName, req.URL.Path) + default: + writeErrorResponse(w, req, InternalError, req.URL.Path) + } + return + } + // generate response + response := generateAccessControlPolicyResponse(bucketMetadata.ACL) + encodedSuccessResponse := encodeSuccessResponse(response) + // write headers + setCommonHeaders(w, len(encodedSuccessResponse)) + // write body + w.Write(encodedSuccessResponse) +} + // HeadBucketHandler - HEAD Bucket // ---------- // This operation is useful to determine if a bucket exists. @@ -440,8 +479,6 @@ func (api API) HeadBucketHandler(w http.ResponseWriter, req *http.Request) { if err != nil { errorIf(err.Trace(), "GetBucketMetadata failed.", nil) switch err.ToGoError().(type) { - case signv4.DoesNotMatch: - writeErrorResponse(w, req, SignatureDoesNotMatch, req.URL.Path) case donut.BucketNotFound: writeErrorResponse(w, req, NoSuchBucket, req.URL.Path) case donut.BucketNameInvalid: diff --git a/server-api-definitions.go b/server-api-definitions.go index 67467cb45..4a890f033 100644 --- a/server-api-definitions.go +++ b/server-api-definitions.go @@ -23,6 +23,26 @@ const ( maxObjectList = 1000 ) +// AccessControlPolicyResponse - format for get bucket acl response +type AccessControlPolicyResponse struct { + AccessControlList struct { + Grant []Grant + } + Owner Owner +} + +// Grant container for grantee and permission +type Grant struct { + Grantee struct { + ID string + DisplayName string + EmailAddress string + Type string + URI string + } + Permission string +} + // ListObjectsResponse - format for list objects response type ListObjectsResponse struct { XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"` @@ -182,6 +202,7 @@ var notimplementedBucketResourceNames = map[string]bool{ "location": true, "logging": true, "notification": true, + "replication": true, "tagging": true, "versions": true, "requestPayment": true, diff --git a/server-api-response.go b/server-api-response.go index 5bdcfa97e..97fc7a613 100644 --- a/server-api-response.go +++ b/server-api-response.go @@ -54,14 +54,44 @@ func generateListBucketsResponse(buckets []donut.BucketMetadata) ListBucketsResp return data } -// takes a set of objects and prepares the objects for serialization -// input: -// bucket name -// array of object metadata -// results truncated flag -// -// output: -// populated struct that can be serialized to match xml and json api spec output +// generates an AccessControlPolicy response for the said ACL. +func generateAccessControlPolicyResponse(acl donut.BucketACL) AccessControlPolicyResponse { + accessCtrlPolicyResponse := AccessControlPolicyResponse{} + accessCtrlPolicyResponse.Owner = Owner{ + ID: "minio", + DisplayName: "minio", + } + defaultGrant := Grant{} + defaultGrant.Grantee.ID = "minio" + defaultGrant.Grantee.DisplayName = "minio" + defaultGrant.Permission = "FULL_CONTROL" + accessCtrlPolicyResponse.AccessControlList.Grant = append(accessCtrlPolicyResponse.AccessControlList.Grant, defaultGrant) + switch { + case acl.IsPublicRead(): + publicReadGrant := Grant{} + publicReadGrant.Grantee.ID = "minio" + publicReadGrant.Grantee.DisplayName = "minio" + publicReadGrant.Grantee.URI = "http://acs.amazonaws.com/groups/global/AllUsers" + publicReadGrant.Permission = "READ" + accessCtrlPolicyResponse.AccessControlList.Grant = append(accessCtrlPolicyResponse.AccessControlList.Grant, publicReadGrant) + case acl.IsPublicReadWrite(): + publicReadGrant := Grant{} + publicReadGrant.Grantee.ID = "minio" + publicReadGrant.Grantee.DisplayName = "minio" + publicReadGrant.Grantee.URI = "http://acs.amazonaws.com/groups/global/AllUsers" + publicReadGrant.Permission = "READ" + publicReadWriteGrant := Grant{} + publicReadWriteGrant.Grantee.ID = "minio" + publicReadWriteGrant.Grantee.DisplayName = "minio" + publicReadWriteGrant.Grantee.URI = "http://acs.amazonaws.com/groups/global/AllUsers" + publicReadWriteGrant.Permission = "WRITE" + accessCtrlPolicyResponse.AccessControlList.Grant = append(accessCtrlPolicyResponse.AccessControlList.Grant, publicReadGrant) + accessCtrlPolicyResponse.AccessControlList.Grant = append(accessCtrlPolicyResponse.AccessControlList.Grant, publicReadWriteGrant) + } + return accessCtrlPolicyResponse +} + +// generates an ListObjects response for the said bucket with other enumerated options. func generateListObjectsResponse(bucket string, objects []donut.ObjectMetadata, bucketResources donut.BucketResourcesMetadata) ListObjectsResponse { var contents []*Object var prefixes []*CommonPrefix diff --git a/server-api-signature-handler.go b/server-api-signature-handler.go index 87967a1a2..93e029664 100644 --- a/server-api-signature-handler.go +++ b/server-api-signature-handler.go @@ -51,9 +51,9 @@ func isRequestPresignedSignatureV4(req *http.Request) bool { func (s signatureHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var signature *signv4.Signature if isRequestSignatureV4(r) { - // If the request is not a PUT method handle the verification here. - // For PUT and POST requests with payload, send the call upwards for verification - if r.Method != "PUT" && r.Method != "POST" { + // For PUT and POST requests with payload, send the call upwards for verification. + // Or PUT and POST requests without payload, verify here. + if (r.Body == nil && (r.Method == "PUT" || r.Method == "POST")) || (r.Method != "PUT" && r.Method != "POST") { // Init signature V4 verification var err *probe.Error signature, err = initSignatureV4(r) diff --git a/server-router.go b/server-router.go index 7615af296..568867704 100644 --- a/server-router.go +++ b/server-router.go @@ -28,6 +28,7 @@ import ( // registerAPI - register all the object API handlers to their respective paths func registerAPI(mux *router.Router, a API) { mux.HandleFunc("/", a.ListBucketsHandler).Methods("GET") + mux.HandleFunc("/{bucket}", a.GetBucketACLHandler).Queries("acl", "").Methods("GET") mux.HandleFunc("/{bucket}", a.ListObjectsHandler).Methods("GET") mux.HandleFunc("/{bucket}", a.PutBucketACLHandler).Queries("acl", "").Methods("PUT") mux.HandleFunc("/{bucket}", a.PutBucketHandler).Methods("PUT")