12345678module cli910import json211import os12import time13import src.history14import src.snapshot15import src.storage1617pub fn cmd_doctor(raw []string) int {18 args := parse('doctor', raw, Options{19 values: ['backup']20 flags: ['json']21 }) or { return usage_error(err.msg()) }22 store := open_store() or { return refuse(args.json(), err.msg()) }23 defer {24 store.close()25 }26 mut report := DoctorJson{27 problems: []string{}28 }2930 report.problems << store.integrity_check() or { return refuse(args.json(), err.msg()) }31 report.problems << store.check_objects() or { return refuse(args.json(), err.msg()) }32 report.problems << store.check_save_files() or { return refuse(args.json(), err.msg()) }33 report.problems << unsafe_save_paths(store) or { return refuse(args.json(), err.msg()) }3435 report.objects = int(store.object_count() or { 0 })36 report.saves = int(history.save_count(store) or { 0 })37 report.checkpoints = int(history.checkpoint_count(store) or { 0 })38 report.cached_files = int(store.cache_count() or { 0 })39 report.peek_folders_removed = sweep_peek_folders()40 store.drop_cache() or { return refuse(args.json(), err.msg()) }4142 if path := args.value_or_none('backup') {43 if os.exists(path) {44 return refuse(args.json(), '${path} already exists')45 }46 store.backup(path) or { return refuse(args.json(), err.msg()) }47 report.backup = path48 }4950 if args.json() {51 emit_json(json2.encode(report))52 } else {53 if report.problems.len == 0 {54 ok('${store.root} is sound')55 } else {56 for problem in report.problems {57 warn(problem)58 }59 }60 note('${report.objects} objects · ${report.saves} saves · ${report.checkpoints} checkpoints')61 if report.backup != '' {62 ok('backed up to ${report.backup}')63 }64 }65 if report.problems.len > 0 {66 return 167 }68 return 069}7071fn unsafe_save_paths(store &storage.Store) ![]string {72 mut problems := []string{}73 for save in history.line(store)! {74 for file in history.files_of(store, save.id)! {75 if !snapshot.legal_path(file.path) {76 problems << 'save [${save.id}] has unsafe path ${snapshot.escape_path(file.path)} — koh strip ${shell_quote(file.path)}'77 }78 }79 }80 return problems81}8283fn shell_quote(text string) string {84 return "'${text.replace("'", "'\\''")}'"85}868788const peek_age = 8640089909192fn sweep_peek_folders() int {93 mut removed := 094 for name in os.ls(os.temp_dir()) or { return 0 } {95 path := os.join_path(os.temp_dir(), name)96 if !name.starts_with(peek_prefix)97 || !peek_is_old(os.file_last_mod_unix(path), time.now().unix()) {98 continue99 }100 os.rmdir_all(path) or { continue }101 removed++102 }103 return removed104}105106107108fn peek_is_old(modified i64, now i64) bool {109 return modified < now - peek_age110}111112struct DoctorJson {113mut:114 problems []string115 objects int116 saves int117 checkpoints int118 cached_files int119 peek_folders_removed int120 backup string121}