github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/reporter_kbpki.go (about) 1 // Copyright 2016 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libkbfs 6 7 import ( 8 "fmt" 9 "strconv" 10 "sync" 11 "time" 12 13 "github.com/keybase/client/go/kbfs/data" 14 "github.com/keybase/client/go/kbfs/idutil" 15 "github.com/keybase/client/go/kbfs/kbfsmd" 16 "github.com/keybase/client/go/kbfs/tlf" 17 "github.com/keybase/client/go/kbfs/tlfhandle" 18 "github.com/keybase/client/go/libkb" 19 "github.com/keybase/client/go/logger" 20 "github.com/keybase/client/go/protocol/keybase1" 21 "github.com/pkg/errors" 22 "golang.org/x/net/context" 23 ) 24 25 const ( 26 // error param keys 27 errorParamTlf = "tlf" 28 errorParamMode = "mode" 29 errorParamUsername = "username" 30 errorParamRekeySelf = "rekeyself" 31 errorParamUsageBytes = "usageBytes" 32 errorParamLimitBytes = "limitBytes" 33 errorParamUsageFiles = "usageFiles" 34 errorParamLimitFiles = "limitFiles" 35 errorParamFoldersCreated = "foldersCreated" 36 errorParamFolderLimit = "folderLimit" 37 errorParamApplicationExecPath = "applicationExecPath" 38 39 // error operation modes 40 errorModeRead = "read" 41 errorModeWrite = "write" 42 ) 43 44 const connectionStatusConnected keybase1.FSStatusCode = keybase1.FSStatusCode_START 45 const connectionStatusDisconnected keybase1.FSStatusCode = keybase1.FSStatusCode_ERROR 46 47 // ReporterKBPKI implements the Notify function of the Reporter 48 // interface in addition to embedding ReporterSimple for error 49 // tracking. Notify will make RPCs to the keybase daemon. 50 type ReporterKBPKI struct { 51 *ReporterSimple 52 config Config 53 log logger.Logger 54 vlog *libkb.VDebugLog 55 notifyBuffer chan *keybase1.FSNotification 56 onlineStatusBuffer chan bool 57 notifyPathBuffer chan string 58 notifySyncBuffer chan *keybase1.FSPathSyncStatus 59 notifyOverallSyncBuffer chan keybase1.FolderSyncStatus 60 notifyFavsBuffer chan struct{} 61 shutdownCh chan struct{} 62 canceler func() 63 64 lastNotifyPathLock sync.Mutex 65 lastNotifyPath string 66 67 shutdownLock sync.RWMutex 68 isShutdown bool 69 } 70 71 // NewReporterKBPKI creates a new ReporterKBPKI. 72 func NewReporterKBPKI(config Config, maxErrors, bufSize int) *ReporterKBPKI { 73 log := config.MakeLogger("") 74 r := &ReporterKBPKI{ 75 ReporterSimple: NewReporterSimple(config.Clock(), maxErrors), 76 config: config, 77 log: log, 78 vlog: config.MakeVLogger(log), 79 notifyBuffer: make(chan *keybase1.FSNotification, bufSize), 80 onlineStatusBuffer: make(chan bool, bufSize), 81 notifyPathBuffer: make(chan string, 1), 82 notifySyncBuffer: make(chan *keybase1.FSPathSyncStatus, 1), 83 notifyOverallSyncBuffer: make(chan keybase1.FolderSyncStatus, 1), 84 notifyFavsBuffer: make(chan struct{}, 1), 85 shutdownCh: make(chan struct{}), 86 } 87 var ctx context.Context 88 ctx, r.canceler = context.WithCancel(context.Background()) 89 go r.send(ctx) 90 return r 91 } 92 93 // ReportErr implements the Reporter interface for ReporterKBPKI. 94 func (r *ReporterKBPKI) ReportErr(ctx context.Context, 95 tlfName tlf.CanonicalName, t tlf.Type, mode ErrorModeType, err error) { 96 r.ReporterSimple.ReportErr(ctx, tlfName, t, mode, err) 97 98 // Fire off error popups 99 params := make(map[string]string) 100 filename := "" 101 var code keybase1.FSErrorType = -1 102 switch e := errors.Cause(err).(type) { 103 case tlfhandle.ReadAccessError: 104 code = keybase1.FSErrorType_ACCESS_DENIED 105 params[errorParamMode] = errorModeRead 106 filename = e.Filename 107 case tlfhandle.WriteAccessError: 108 code = keybase1.FSErrorType_ACCESS_DENIED 109 params[errorParamUsername] = e.User.String() 110 params[errorParamMode] = errorModeWrite 111 filename = e.Filename 112 case WriteUnsupportedError: 113 code = keybase1.FSErrorType_ACCESS_DENIED 114 params[errorParamMode] = errorModeWrite 115 filename = e.Filename 116 case UnverifiableTlfUpdateError: 117 code = keybase1.FSErrorType_REVOKED_DATA_DETECTED 118 case idutil.NoCurrentSessionError: 119 code = keybase1.FSErrorType_NOT_LOGGED_IN 120 case NeedSelfRekeyError: 121 code = keybase1.FSErrorType_REKEY_NEEDED 122 params[errorParamRekeySelf] = "true" 123 case NeedOtherRekeyError: 124 code = keybase1.FSErrorType_REKEY_NEEDED 125 params[errorParamRekeySelf] = "false" 126 case kbfsmd.NewMetadataVersionError: 127 code = keybase1.FSErrorType_OLD_VERSION 128 err = OutdatedVersionError{} 129 case kbfsmd.NewMerkleVersionError: 130 code = keybase1.FSErrorType_OLD_VERSION 131 err = OutdatedVersionError{} 132 case NewDataVersionError: 133 code = keybase1.FSErrorType_OLD_VERSION 134 err = OutdatedVersionError{} 135 case OverQuotaWarning: 136 code = keybase1.FSErrorType_OVER_QUOTA 137 params[errorParamUsageBytes] = strconv.FormatInt(e.UsageBytes, 10) 138 params[errorParamLimitBytes] = strconv.FormatInt(e.LimitBytes, 10) 139 case *ErrDiskLimitTimeout: 140 if !e.reportable { 141 return 142 } 143 code = keybase1.FSErrorType_DISK_LIMIT_REACHED 144 params[errorParamUsageBytes] = strconv.FormatInt(e.usageBytes, 10) 145 params[errorParamLimitBytes] = 146 strconv.FormatFloat(e.limitBytes, 'f', 0, 64) 147 params[errorParamUsageFiles] = strconv.FormatInt(e.usageFiles, 10) 148 params[errorParamLimitFiles] = 149 strconv.FormatFloat(e.limitFiles, 'f', 0, 64) 150 case idutil.NoSigChainError: 151 code = keybase1.FSErrorType_NO_SIG_CHAIN 152 params[errorParamUsername] = e.User.String() 153 case kbfsmd.ServerErrorTooManyFoldersCreated: 154 code = keybase1.FSErrorType_TOO_MANY_FOLDERS 155 params[errorParamFolderLimit] = strconv.FormatUint(e.Limit, 10) 156 params[errorParamFoldersCreated] = strconv.FormatUint(e.Created, 10) 157 case RenameAcrossDirsError: 158 if len(e.ApplicationExecPath) > 0 { 159 code = keybase1.FSErrorType_EXDEV_NOT_SUPPORTED 160 params[errorParamApplicationExecPath] = e.ApplicationExecPath 161 } 162 case OfflineArchivedError: 163 code = keybase1.FSErrorType_OFFLINE_ARCHIVED 164 case OfflineUnsyncedError: 165 code = keybase1.FSErrorType_OFFLINE_UNSYNCED 166 } 167 168 if code < 0 && err == context.DeadlineExceeded { 169 code = keybase1.FSErrorType_TIMEOUT 170 // Workaround for DESKTOP-2442 171 filename = string(tlfName) 172 } 173 174 if code >= 0 { 175 n := errorNotification(err, code, tlfName, t, mode, filename, params) 176 r.Notify(ctx, n) 177 r.config.GetPerfLog().CDebugf(ctx, "KBFS error: %v", err) 178 } 179 } 180 181 // Notify implements the Reporter interface for ReporterKBPKI. 182 // 183 // TODO: might be useful to get the debug tags out of ctx and store 184 // 185 // them in the notifyBuffer as well so that send() can put 186 // them back in its context. 187 func (r *ReporterKBPKI) Notify(ctx context.Context, notification *keybase1.FSNotification) { 188 r.shutdownLock.RLock() 189 defer r.shutdownLock.RUnlock() 190 if r.isShutdown { 191 return 192 } 193 194 select { 195 case r.notifyBuffer <- notification: 196 default: 197 r.vlog.CLogf( 198 ctx, libkb.VLog1, "ReporterKBPKI: notify buffer full, dropping %+v", 199 notification) 200 } 201 } 202 203 // OnlineStatusChanged notifies the service (and eventually GUI) when we 204 // detected we are connected to or disconnected from mdserver. 205 func (r *ReporterKBPKI) OnlineStatusChanged(ctx context.Context, online bool) { 206 r.shutdownLock.RLock() 207 defer r.shutdownLock.RUnlock() 208 if r.isShutdown { 209 return 210 } 211 212 r.onlineStatusBuffer <- online 213 } 214 215 func (r *ReporterKBPKI) setLastNotifyPath(p string) (same bool) { 216 r.lastNotifyPathLock.Lock() 217 defer r.lastNotifyPathLock.Unlock() 218 same = p == r.lastNotifyPath 219 r.lastNotifyPath = p 220 return same 221 } 222 223 // NotifyPathUpdated implements the Reporter interface for ReporterKBPKI. 224 // 225 // TODO: might be useful to get the debug tags out of ctx and store 226 // 227 // them in the notifyPathBuffer as well so that send() can put 228 // them back in its context. 229 func (r *ReporterKBPKI) NotifyPathUpdated(ctx context.Context, path string) { 230 r.shutdownLock.RLock() 231 defer r.shutdownLock.RUnlock() 232 if r.isShutdown { 233 return 234 } 235 236 sameAsLast := r.setLastNotifyPath(path) 237 select { 238 case r.notifyPathBuffer <- path: 239 default: 240 if sameAsLast { 241 r.vlog.CLogf( 242 ctx, libkb.VLog1, 243 "ReporterKBPKI: notify path buffer full, dropping %s", path) 244 } else { 245 // This should be rare; it only happens when user switches from one 246 // TLF to another, and we happen to have an update from the old TLF 247 // in the buffer before switching subscribed TLF. 248 r.vlog.CLogf( 249 ctx, libkb.VLog1, 250 "ReporterKBPKI: notify path buffer full, but path is "+ 251 "different from last one, so send in a goroutine %s", path) 252 go func() { 253 r.shutdownLock.RLock() 254 defer r.shutdownLock.RUnlock() 255 if r.isShutdown { 256 return 257 } 258 259 select { 260 case r.notifyPathBuffer <- path: 261 case <-r.shutdownCh: 262 } 263 }() 264 } 265 } 266 } 267 268 // NotifySyncStatus implements the Reporter interface for ReporterKBPKI. 269 // 270 // TODO: might be useful to get the debug tags out of ctx and store 271 // 272 // them in the notifyBuffer as well so that send() can put 273 // them back in its context. 274 func (r *ReporterKBPKI) NotifySyncStatus(ctx context.Context, 275 status *keybase1.FSPathSyncStatus) { 276 r.shutdownLock.RLock() 277 defer r.shutdownLock.RUnlock() 278 if r.isShutdown { 279 return 280 } 281 282 select { 283 case r.notifySyncBuffer <- status: 284 default: 285 r.vlog.CLogf( 286 ctx, libkb.VLog1, "ReporterKBPKI: notify sync buffer full, "+ 287 "dropping %+v", status) 288 } 289 } 290 291 // NotifyFavoritesChanged implements the Reporter interface for 292 // ReporterSimple. 293 func (r *ReporterKBPKI) NotifyFavoritesChanged(ctx context.Context) { 294 r.shutdownLock.RLock() 295 defer r.shutdownLock.RUnlock() 296 if r.isShutdown { 297 return 298 } 299 300 select { 301 case r.notifyFavsBuffer <- struct{}{}: 302 default: 303 r.vlog.CLogf( 304 ctx, libkb.VLog1, "ReporterKBPKI: notify favs buffer full, "+ 305 "dropping") 306 } 307 } 308 309 // NotifyOverallSyncStatus implements the Reporter interface for ReporterKBPKI. 310 func (r *ReporterKBPKI) NotifyOverallSyncStatus( 311 ctx context.Context, status keybase1.FolderSyncStatus) { 312 r.shutdownLock.RLock() 313 defer r.shutdownLock.RUnlock() 314 if r.isShutdown { 315 return 316 } 317 318 select { 319 case r.notifyOverallSyncBuffer <- status: 320 default: 321 // If this represents a "complete" status, we can't drop it. 322 // Instead launch a goroutine to make sure it gets sent 323 // eventually. 324 if status.PrefetchStatus == keybase1.PrefetchStatus_COMPLETE { 325 go func() { 326 r.shutdownLock.RLock() 327 defer r.shutdownLock.RUnlock() 328 if r.isShutdown { 329 return 330 } 331 332 select { 333 case r.notifyOverallSyncBuffer <- status: 334 case <-r.shutdownCh: 335 } 336 }() 337 } else { 338 r.vlog.CLogf( 339 ctx, libkb.VLog1, 340 "ReporterKBPKI: notify overall sync buffer dropping %+v", 341 status) 342 } 343 } 344 } 345 346 // Shutdown implements the Reporter interface for ReporterKBPKI. 347 func (r *ReporterKBPKI) Shutdown() { 348 r.shutdownLock.Lock() 349 defer r.shutdownLock.Unlock() 350 if r.isShutdown { 351 return 352 } 353 r.isShutdown = true 354 355 r.canceler() 356 close(r.shutdownCh) 357 close(r.notifyBuffer) 358 close(r.onlineStatusBuffer) 359 close(r.notifySyncBuffer) 360 close(r.notifyOverallSyncBuffer) 361 close(r.notifyFavsBuffer) 362 } 363 364 const ( 365 reporterSendInterval = time.Second 366 reporterFavSendInterval = 5 * time.Second 367 ) 368 369 // send takes notifications out of notifyBuffer, notifyPathBuffer, and 370 // notifySyncBuffer and sends them to the keybase daemon. 371 func (r *ReporterKBPKI) send(ctx context.Context) { 372 sendTicker := time.NewTicker(reporterSendInterval) 373 defer sendTicker.Stop() 374 favSendTicker := time.NewTicker(reporterFavSendInterval) 375 defer favSendTicker.Stop() 376 377 for { 378 select { 379 case notification, ok := <-r.notifyBuffer: 380 if !ok { 381 return 382 } 383 nt := notification.NotificationType 384 st := notification.StatusCode 385 // Only these notifications are used in frontend: 386 // https://github.com/keybase/client/blob/0d63795105f64289ba4ef20fbefe56aad91bc7e9/shared/util/kbfs-notifications.js#L142-L154 387 if nt != keybase1.FSNotificationType_REKEYING && 388 nt != keybase1.FSNotificationType_INITIALIZED && 389 nt != keybase1.FSNotificationType_CONNECTION && 390 nt != keybase1.FSNotificationType_SYNC_CONFIG_CHANGED && 391 st != keybase1.FSStatusCode_ERROR { 392 continue 393 } 394 // Send them right away rather than staging it and waiting for the 395 // ticker, since each of them can be distinct from each other. 396 if err := r.config.KeybaseService().Notify(ctx, 397 notification); err != nil { 398 r.log.CDebugf(ctx, "ReporterDaemon: error sending "+ 399 "notification: %s", err) 400 } 401 case online, ok := <-r.onlineStatusBuffer: 402 if !ok { 403 return 404 } 405 if err := r.config.KeybaseService().NotifyOnlineStatusChanged(ctx, online); err != nil { 406 r.log.CDebugf(ctx, "ReporterDaemon: error sending "+ 407 "NotifyOnlineStatusChanged: %s", err) 408 } 409 case <-sendTicker.C: 410 select { 411 case path, ok := <-r.notifyPathBuffer: 412 if !ok { 413 return 414 } 415 if err := r.config.KeybaseService().NotifyPathUpdated( 416 ctx, path); err != nil { 417 r.log.CDebugf(ctx, "ReporterDaemon: error sending "+ 418 "notification for path: %s", err) 419 } 420 default: 421 } 422 423 select { 424 case status, ok := <-r.notifySyncBuffer: 425 if !ok { 426 return 427 } 428 if err := r.config.KeybaseService().NotifySyncStatus(ctx, 429 status); err != nil { 430 r.log.CDebugf(ctx, "ReporterDaemon: error sending "+ 431 "sync status: %s", err) 432 } 433 default: 434 } 435 436 select { 437 case status, ok := <-r.notifyOverallSyncBuffer: 438 if !ok { 439 return 440 } 441 if err := r.config.KeybaseService().NotifyOverallSyncStatus( 442 ctx, status); err != nil { 443 r.log.CDebugf(ctx, "ReporterDaemon: error sending "+ 444 "overall sync status: %s", err) 445 } 446 default: 447 } 448 case <-favSendTicker.C: 449 select { 450 case _, ok := <-r.notifyFavsBuffer: 451 if !ok { 452 return 453 } 454 if err := r.config.KeybaseService().NotifyFavoritesChanged( 455 ctx); err != nil { 456 r.log.CDebugf(ctx, "ReporterDaemon: error sending "+ 457 "favorites changed notification: %s", err) 458 } 459 default: 460 } 461 case <-ctx.Done(): 462 return 463 } 464 } 465 } 466 467 // writeNotification creates FSNotifications from paths for file 468 // write events. 469 func writeNotification(file data.Path, finish bool) *keybase1.FSNotification { 470 n := baseNotification(file, finish) 471 if file.Tlf.Type() == tlf.Public { 472 n.NotificationType = keybase1.FSNotificationType_SIGNING 473 } else { 474 n.NotificationType = keybase1.FSNotificationType_ENCRYPTING 475 } 476 return n 477 } 478 479 // readNotification creates FSNotifications from paths for file 480 // read events. 481 func readNotification(file data.Path, finish bool) *keybase1.FSNotification { 482 n := baseNotification(file, finish) 483 if file.Tlf.Type() == tlf.Public { 484 n.NotificationType = keybase1.FSNotificationType_VERIFYING 485 } else { 486 n.NotificationType = keybase1.FSNotificationType_DECRYPTING 487 } 488 return n 489 } 490 491 // rekeyNotification creates FSNotifications from TlfHandles for rekey 492 // events. 493 func rekeyNotification(ctx context.Context, config Config, handle *tlfhandle.Handle, finish bool) *keybase1.FSNotification { 494 code := keybase1.FSStatusCode_START 495 if finish { 496 code = keybase1.FSStatusCode_FINISH 497 } 498 499 return &keybase1.FSNotification{ 500 FolderType: handle.Type().FolderType(), 501 Filename: handle.GetCanonicalPath(), 502 StatusCode: code, 503 NotificationType: keybase1.FSNotificationType_REKEYING, 504 } 505 } 506 507 // connectionNotification creates FSNotifications based on whether 508 // or not KBFS is online. 509 func connectionNotification(status keybase1.FSStatusCode) *keybase1.FSNotification { 510 // TODO finish placeholder 511 return &keybase1.FSNotification{ 512 NotificationType: keybase1.FSNotificationType_CONNECTION, 513 StatusCode: status, 514 } 515 } 516 517 // baseNotification creates a basic FSNotification without a 518 // NotificationType from a path. 519 func baseNotification(file data.Path, finish bool) *keybase1.FSNotification { 520 code := keybase1.FSStatusCode_START 521 if finish { 522 code = keybase1.FSStatusCode_FINISH 523 } 524 525 return &keybase1.FSNotification{ 526 Filename: file.CanonicalPathPlaintext(), 527 StatusCode: code, 528 } 529 } 530 531 // errorNotification creates FSNotifications for errors. 532 func errorNotification(err error, errType keybase1.FSErrorType, 533 tlfName tlf.CanonicalName, t tlf.Type, mode ErrorModeType, 534 filename string, params map[string]string) *keybase1.FSNotification { 535 if tlfName != "" { 536 params[errorParamTlf] = string(tlfName) 537 } 538 var nType keybase1.FSNotificationType 539 switch mode { 540 case ReadMode: 541 params[errorParamMode] = errorModeRead 542 if t == tlf.Public { 543 nType = keybase1.FSNotificationType_VERIFYING 544 } else { 545 nType = keybase1.FSNotificationType_DECRYPTING 546 } 547 case WriteMode: 548 params[errorParamMode] = errorModeWrite 549 if t == tlf.Public { 550 nType = keybase1.FSNotificationType_SIGNING 551 } else { 552 nType = keybase1.FSNotificationType_ENCRYPTING 553 } 554 default: 555 panic(fmt.Sprintf("Unknown mode: %v", mode)) 556 } 557 return &keybase1.FSNotification{ 558 FolderType: t.FolderType(), 559 Filename: filename, 560 StatusCode: keybase1.FSStatusCode_ERROR, 561 Status: err.Error(), 562 ErrorType: errType, 563 Params: params, 564 NotificationType: nType, 565 } 566 } 567 568 func mdReadSuccessNotification(handle *tlfhandle.Handle, 569 public bool) *keybase1.FSNotification { 570 params := make(map[string]string) 571 if handle != nil { 572 params[errorParamTlf] = string(handle.GetCanonicalName()) 573 } 574 return &keybase1.FSNotification{ 575 FolderType: handle.Type().FolderType(), 576 Filename: handle.GetCanonicalPath(), 577 StatusCode: keybase1.FSStatusCode_START, 578 NotificationType: keybase1.FSNotificationType_MD_READ_SUCCESS, 579 Params: params, 580 } 581 } 582 583 func syncConfigChangeNotification(handle *tlfhandle.Handle, 584 fsc keybase1.FolderSyncConfig) *keybase1.FSNotification { 585 params := map[string]string{ 586 "syncMode": fsc.Mode.String(), 587 } 588 return &keybase1.FSNotification{ 589 FolderType: handle.Type().FolderType(), 590 Filename: handle.GetCanonicalPath(), 591 StatusCode: keybase1.FSStatusCode_START, 592 NotificationType: keybase1.FSNotificationType_SYNC_CONFIG_CHANGED, 593 Params: params, 594 } 595 }