@@ -35,6 +35,7 @@ import (
3535 "regexp"
3636 "strconv"
3737 "strings"
38+ "sync"
3839 "time"
3940
4041 "github.com/snapcore/snapd/asserts"
@@ -67,6 +68,8 @@ type snapCachedInfo struct {
6768
6869// Store is our snappy software store implementation
6970type Store struct {
71+ lock sync.Mutex
72+
7073 url string
7174 blobDir string
7275 assertDir string
@@ -80,6 +83,9 @@ type Store struct {
8083
8184 snapsCache map [string ]snapCachedInfo
8285
86+ // endpoint -> quota value, note this is stateful, i.e. the quota is counted
87+ // for all requests to a given endpoint and after exceeding it, all
88+ // subsequent requests will fail until it is reset though a request
8389 killAfter map [string ]int64
8490}
8591
@@ -397,12 +403,16 @@ type debugResultJSON struct {
397403
398404func (s * Store ) debugEndpoint (w http.ResponseWriter , req * http.Request ) {
399405 if req .Method == http .MethodGet {
400- res := debugResultJSON {
401- KillAfter : s .killAfter ,
402- }
403- out , err := json .MarshalIndent (res , "" , " " )
406+ out , err := func () ([]byte , error ) {
407+ s .lock .Lock ()
408+ defer s .lock .Unlock ()
409+ res := debugResultJSON {
410+ KillAfter : s .killAfter ,
411+ }
412+ return json .Marshal (res )
413+ }()
404414 if err != nil {
405- http .Error (w , fmt .Sprintf ("cannot marshal: %v: %v" , res , err ), 500 )
415+ http .Error (w , fmt .Sprintf ("cannot marshal: %v" , err ), 500 )
406416 return
407417 }
408418 w .Write (out )
@@ -421,21 +431,46 @@ func (s *Store) debugEndpoint(w http.ResponseWriter, req *http.Request) {
421431 return
422432 }
423433
434+ var err error
424435 switch debugReq .Action {
425436 case "kill-request" :
426- s .debugActionKillDownload (debugReq )
437+ err = s .debugActionKillDownload (debugReq )
438+ case "reset" :
439+ s .debugActionReset (debugReq )
427440 default :
441+ err = fmt .Errorf ("unexpected debug action %q" , debugReq .Action )
442+ }
443+ if err != nil {
428444 w .WriteHeader (400 )
429- fmt .Fprintf (w , "unexpected debug action %q" , debugReq . Action )
445+ fmt .Fprint (w , err . Error () )
430446 }
431447}
432448
433- func (s * Store ) debugActionKillDownload (debugReq * debugRequestJSON ) {
449+ func (s * Store ) debugActionKillDownload (debugReq * debugRequestJSON ) error {
450+ if debugReq .KillPath == "" {
451+ return fmt .Errorf ("kill-path cannot be empty" )
452+ }
453+
454+ if strings .HasPrefix (debugReq .KillPath , "/debug/" ) {
455+ return fmt .Errorf ("kill-path cannot be applied to /debug/ endpoints" )
456+ }
457+
458+ s .lock .Lock ()
459+ defer s .lock .Unlock ()
460+
434461 if debugReq .KillAfter == 0 {
435462 delete (s .killAfter , debugReq .KillPath )
436- return
463+ } else {
464+ s .killAfter [debugReq .KillPath ] = debugReq .KillAfter
437465 }
438- s .killAfter [debugReq .KillPath ] = debugReq .KillAfter
466+ return nil
467+ }
468+
469+ func (s * Store ) debugActionReset (debugReq * debugRequestJSON ) {
470+ s .lock .Lock ()
471+ defer s .lock .Unlock ()
472+
473+ s .killAfter = map [string ]int64 {}
439474}
440475
441476func logRangeHeader (handler http.HandlerFunc ) http.HandlerFunc {
@@ -451,7 +486,14 @@ func logRangeHeader(handler http.HandlerFunc) http.HandlerFunc {
451486func (s * Store ) applyKillAfter (handler http.HandlerFunc ) http.HandlerFunc {
452487 return func (w http.ResponseWriter , req * http.Request ) {
453488 path := req .URL .Path
454- killAfter , exists := s .killAfter [path ]
489+
490+ killAfter , exists := func () (int64 , bool ) {
491+ s .lock .Lock ()
492+ defer s .lock .Unlock ()
493+ v , ok := s .killAfter [path ]
494+ return v , ok
495+ }()
496+
455497 if ! exists {
456498 handler (w , req )
457499 return
@@ -468,7 +510,11 @@ func (s *Store) applyKillAfter(handler http.HandlerFunc) http.HandlerFunc {
468510 }
469511
470512 // update killAfter for path after write finishes
471- s .killAfter [path ] = kaw .killAfter
513+ func () {
514+ s .lock .Lock ()
515+ defer s .lock .Unlock ()
516+ s .killAfter [path ] = kaw .killAfter
517+ }()
472518 }
473519}
474520
0 commit comments