cmd_doctor.v

3.7 KB · raw

1//! `koh doctor` — integrity check and cleanup.2//!3//! One source of truth means there is nothing to rebuild; what is left to check4//! is that the file is sound and that every object a save names is really5//! there. Doctor also tidies the things that are safe to lose: the file cache6//! and, with --yes, old peek folders.78module cli910import json211import os12import time13import src.fmt14import src.history15import src.snapshot16import src.storage1718pub fn cmd_doctor(raw []string) int {19	args := parse('doctor', raw, Options{20		values: ['backup']21		flags:  ['json', 'yes']22	}) or { return usage_error(err.msg()) }23	store := open_store() or { return refuse(args.json(), err.msg()) }24	defer {25		store.close()26	}27	mut report := DoctorJson{28		problems: []string{}29	}3031	working('checking ${fmt.plural(int(store.object_count() or { 0 }), 'object')} and every save')32	report.problems << store.integrity_check() or { return refuse(args.json(), err.msg()) }33	report.problems << store.check_objects() or { return refuse(args.json(), err.msg()) }34	report.problems << store.check_save_files() or { return refuse(args.json(), err.msg()) }35	report.problems << unsafe_save_paths(store) or { return refuse(args.json(), err.msg()) }3637	report.objects = int(store.object_count() or { 0 })38	report.saves = int(history.save_count(store) or { 0 })39	report.checkpoints = int(history.checkpoint_count(store) or { 0 })40	report.cached_files = int(store.cache_count() or { 0 })41	if args.flag('yes') {42		report.peek_folders_removed = sweep_peek_folders()43	}44	store.drop_cache() or { return refuse(args.json(), err.msg()) }4546	if path := args.value_or_none('backup') {47		if os.exists(path) {48			return refuse(args.json(), '${path} already exists')49		}50		store.backup(path) or { return refuse(args.json(), err.msg()) }51		report.backup = path52	}5354	if args.json() {55		emit_json(json2.encode(report))56	} else {57		if report.problems.len == 0 {58			ok('${store.root} is sound')59		} else {60			for problem in report.problems {61				warn(problem)62			}63		}64		note('${fmt.plural(report.objects, 'object')} · ${fmt.plural(report.saves, 'save')} · ${fmt.plural(report.checkpoints, 'checkpoint')}')65		if report.backup != '' {66			ok('backed up to ${report.backup}')67		}68	}69	if report.problems.len > 0 {70		return 171	}72	return 073}7475fn unsafe_save_paths(store &storage.Store) ![]string {76	mut problems := []string{}77	for save in history.line(store)! {78		for file in history.files_of(store, save.id)! {79			if !snapshot.legal_path(file.path) {80				problems << 'save [${save.id}] has unsafe path ${snapshot.escape_path(file.path)} — koh strip ${shell_quote(file.path)}'81			}82		}83	}84	return problems85}8687fn shell_quote(text string) string {88	return "'${text.replace("'", "'\\''")}'"89}9091// peek_age is how old a peek folder must be before doctor clears it.92const peek_age = 864009394// sweep_peek_folders clears the scratch trees `koh peek` leaves in the system95// temp directory, once they are a day old.96fn sweep_peek_folders() int {97	mut removed := 098	for name in os.ls(os.temp_dir()) or { return 0 } {99		path := os.join_path(os.temp_dir(), name)100		info := os.lstat(path) or { continue }101		if !name.starts_with(peek_prefix) || os.is_link(path) || !os.is_dir(path)102			|| info.uid != u32(os.getuid()) || !peek_is_old(os.file_last_mod_unix(path), time.now().unix()) {103			continue104		}105		os.rmdir_all(path) or { continue }106		removed++107	}108	return removed109}110111// peek_is_old is true once a peek is more than a day old. A younger one may112// still be open in somebody's editor.113fn peek_is_old(modified i64, now i64) bool {114	return modified < now - peek_age115}116117struct DoctorJson {118mut:119	problems             []string120	objects              int121	saves                int122	checkpoints          int123	cached_files         int124	peek_folders_removed int125	backup               string126}