github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/instances/checks.go (about) 1 package instances 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "net/http" 7 "strconv" 8 9 "github.com/cozy/cozy-stack/model/instance/lifecycle" 10 "github.com/cozy/cozy-stack/model/sharing" 11 "github.com/cozy/cozy-stack/model/vfs" 12 "github.com/cozy/cozy-stack/pkg/consts" 13 "github.com/cozy/cozy-stack/pkg/couchdb" 14 "github.com/labstack/echo/v4" 15 ) 16 17 func fsckHandler(c echo.Context) (err error) { 18 domain := c.Param("domain") 19 i, err := lifecycle.GetInstance(domain) 20 if err != nil { 21 return wrapError(err) 22 } 23 24 indexIntegrityCheck, _ := strconv.ParseBool(c.QueryParam("IndexIntegrity")) 25 filesConsistencyCheck, _ := strconv.ParseBool(c.QueryParam("FilesConsistency")) 26 failFast, _ := strconv.ParseBool(c.QueryParam("FailFast")) 27 28 logCh := make(chan *vfs.FsckLog) 29 go func() { 30 fs := i.VFS() 31 if indexIntegrityCheck { 32 err = fs.CheckIndexIntegrity(func(log *vfs.FsckLog) { logCh <- log }, failFast) 33 } else if filesConsistencyCheck { 34 err = fs.CheckFilesConsistency(func(log *vfs.FsckLog) { logCh <- log }, failFast) 35 } else { 36 err = fs.Fsck(func(log *vfs.FsckLog) { logCh <- log }, failFast) 37 } 38 close(logCh) 39 }() 40 41 w := c.Response().Writer 42 w.WriteHeader(200) 43 encoder := json.NewEncoder(w) 44 for log := range logCh { 45 // XXX do not serialize to JSON the children and the cozyMetadata, as 46 // it can take more than 64ko and scanner will ignore such lines. 47 if log.FileDoc != nil { 48 log.FileDoc.DirsChildren = nil // It can be filled on type mismatch 49 log.FileDoc.FilesChildren = nil // Idem 50 log.FileDoc.FilesChildrenSize = 0 51 log.FileDoc.Metadata = nil 52 } 53 if log.DirDoc != nil { 54 log.DirDoc.DirsChildren = nil 55 log.DirDoc.FilesChildren = nil 56 log.DirDoc.FilesChildrenSize = 0 57 log.DirDoc.Metadata = nil 58 } 59 if log.VersionDoc != nil { 60 log.VersionDoc.Metadata = nil 61 } 62 if errenc := encoder.Encode(log); errenc != nil { 63 i.Logger().WithNamespace("fsck"). 64 Warnf("Cannot encode to JSON: %s (%v)", errenc, log) 65 } 66 if f, ok := w.(http.Flusher); ok { 67 f.Flush() 68 } 69 } 70 if err != nil { 71 log := map[string]string{"error": err.Error()} 72 if couchdb.IsNotFoundError(err) { 73 log = map[string]string{"type": "no_database", "error": err.Error()} 74 } 75 if errenc := encoder.Encode(log); errenc != nil { 76 i.Logger().WithNamespace("fsck"). 77 Warnf("Cannot encode to JSON: %s (%v)", errenc, log) 78 } 79 if f, ok := w.(http.Flusher); ok { 80 f.Flush() 81 } 82 } 83 return nil 84 } 85 86 func checkTriggers(c echo.Context) error { 87 domain := c.Param("domain") 88 inst, err := lifecycle.GetInstance(domain) 89 if err != nil { 90 return wrapError(err) 91 } 92 93 type TriggerInfo struct { 94 TID string `json:"_id"` 95 Type string `json:"type"` 96 WorkerType string `json:"worker"` 97 Arguments string `json:"arguments"` 98 Debounce string `json:"debounce"` 99 Message json.RawMessage `json:"message"` 100 } 101 var triggers []*TriggerInfo 102 err = couchdb.ForeachDocs(inst, consts.Triggers, func(_ string, data json.RawMessage) error { 103 var t *TriggerInfo 104 if err := json.Unmarshal(data, &t); err != nil { 105 return err 106 } 107 triggers = append(triggers, t) 108 return nil 109 }) 110 if err != nil { 111 if couchdb.IsNotFoundError(err) { 112 return c.JSON(http.StatusOK, []map[string]interface{}{ 113 {"type": "no_database", "error": err.Error()}, 114 }) 115 } 116 return wrapError(err) 117 } 118 119 results := []map[string]interface{}{} 120 for i, left := range triggers { 121 for j, right := range triggers { 122 if i >= j { 123 continue 124 } 125 if left.Type == "@in" { 126 // It doesn't make sense to compare @in triggers as they can 127 // have scheduled at different times 128 continue 129 } 130 if left.Type != right.Type { 131 continue 132 } 133 if left.WorkerType != right.WorkerType { 134 continue 135 } 136 if left.Arguments != right.Arguments { 137 continue 138 } 139 if left.Debounce != right.Debounce { 140 continue 141 } 142 lHasMessage := left.Message != nil 143 rHasMessage := right.Message != nil 144 if lHasMessage != rHasMessage { 145 continue 146 } 147 if lHasMessage && rHasMessage { 148 if !bytes.Equal(left.Message, right.Message) { 149 continue 150 } 151 } 152 results = append(results, map[string]interface{}{ 153 "type": "duplicate", 154 "_id": left.TID, 155 "other_id": right.TID, 156 "trigger": left.Type, 157 "worker": left.WorkerType, 158 "arguments": left.Arguments, 159 "debounce": left.Debounce, 160 "message": string(left.Message), 161 }) 162 break 163 } 164 } 165 166 for _, t := range triggers { 167 if t.Type == "@event" && t.WorkerType == "service" { 168 var msg map[string]interface{} 169 err := json.Unmarshal(t.Message, &msg) 170 if err != nil { 171 results = append(results, map[string]interface{}{ 172 "type": "invalid_message", 173 "_id": t.TID, 174 "trigger": t.Type, 175 "worker": t.WorkerType, 176 "message": string(t.Message), 177 }) 178 continue 179 } 180 if name, _ := msg["name"].(string); name == "" { 181 results = append(results, map[string]interface{}{ 182 "type": "missing_name", 183 "_id": t.TID, 184 "trigger": t.Type, 185 "worker": t.WorkerType, 186 "message": string(t.Message), 187 }) 188 } 189 if slug, _ := msg["slug"].(string); slug == "" { 190 results = append(results, map[string]interface{}{ 191 "type": "missing_slug", 192 "_id": t.TID, 193 "trigger": t.Type, 194 "worker": t.WorkerType, 195 "message": string(t.Message), 196 }) 197 } 198 } 199 } 200 201 return c.JSON(http.StatusOK, results) 202 } 203 204 func checkShared(c echo.Context) error { 205 domain := c.Param("domain") 206 i, err := lifecycle.GetInstance(domain) 207 if err != nil { 208 return wrapError(err) 209 } 210 211 results, err := sharing.CheckShared(i) 212 if err != nil { 213 if couchdb.IsNotFoundError(err) { 214 return c.JSON(http.StatusOK, []map[string]interface{}{ 215 {"type": "no_database", "error": err.Error()}, 216 }) 217 } 218 if _, ok := err.(*json.SyntaxError); ok { 219 return c.JSON(http.StatusOK, []map[string]interface{}{ 220 {"type": "invalid_json", "error": err.Error()}, 221 }) 222 } 223 return wrapError(err) 224 } 225 return c.JSON(http.StatusOK, results) 226 } 227 228 func checkSharings(c echo.Context) error { 229 domain := c.Param("domain") 230 i, err := lifecycle.GetInstance(domain) 231 if err != nil { 232 return wrapError(err) 233 } 234 235 skipFSConsistency, _ := strconv.ParseBool(c.QueryParam("SkipFSConsistency")) 236 237 results, err := sharing.CheckSharings(i, skipFSConsistency) 238 if err != nil { 239 if couchdb.IsNotFoundError(err) { 240 return c.JSON(http.StatusOK, []map[string]interface{}{ 241 {"type": "no_database", "error": err.Error()}, 242 }) 243 } 244 return wrapError(err) 245 } 246 return c.JSON(http.StatusOK, results) 247 }