From 4256fa54187b4eb21b0531b9c3d8ccf00c6d288f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 26 Jun 2025 19:02:28 +0530 Subject: [PATCH] Dont recurse into symlinks Allows us to get rid of expensive tracking to avoid symlink loops. Users can explicitly change dir to a symlinked dir to select from it. --- kittens/choose_files/results.go | 2 +- kittens/choose_files/scan.go | 120 ++++++++++++++++---------------- 2 files changed, 60 insertions(+), 62 deletions(-) diff --git a/kittens/choose_files/results.go b/kittens/choose_files/results.go index d61f6f72c..edec49c33 100644 --- a/kittens/choose_files/results.go +++ b/kittens/choose_files/results.go @@ -110,7 +110,7 @@ func icon_for(path string, x os.FileMode) string { return ans } var ans string - if x == fs.ModeDir|1 { + if x&fs.ModeSymlink != 0 && x&SymlinkToDir != 0 { ans = string(icons.SYMLINK_TO_DIR) } else { ans = icons.IconForFileWithMode(path, x, true) diff --git a/kittens/choose_files/scan.go b/kittens/choose_files/scan.go index 511778920..8df6fde64 100644 --- a/kittens/choose_files/scan.go +++ b/kittens/choose_files/scan.go @@ -138,6 +138,8 @@ type sortable_dir_entry struct { buf [unix.NAME_MAX + 1]byte } +const SymlinkToDir = 1 + // lowercase a string into a pre-existing byte buffer with speedups for ASCII func as_lower(s string, output []byte) int { limit := min(len(s), len(output)) @@ -199,8 +201,6 @@ func (fss *FileSystemScanner) worker() { close(l) } }() - seen_dirs := utils.NewSet[string](16 * 1024) - symlink_dir_map := make(map[string]string) root_dir, _ := filepath.Abs(fss.root_dir) dir := root_dir if !strings.HasSuffix(dir, string(os.PathSeparator)) { @@ -212,72 +212,69 @@ func (fss *FileSystemScanner) worker() { var sortable []*sortable_dir_entry var idx uint32 // do a breadth first traversal of the filesystem + is_root := true for dir != "" { if !fss.keep_going.Load() { break } - if !seen_dirs.Has(dir) { - seen_dirs.Add(dir) - entries, err := fss.dir_reader(dir) - if err != nil { - if seen_dirs.Len() == 1 { - fss.keep_going.Store(false) - fss.lock() - fss.err = err - fss.unlock() + entries, err := fss.dir_reader(dir) + if err != nil { + if is_root { + fss.keep_going.Store(false) + fss.lock() + fss.err = err + fss.unlock() + } + entries = nil + } + if cap(arena) < len(entries) { + arena = make([]sortable_dir_entry, 0, max(1024, len(entries), 2*cap(arena))) + sortable = make([]*sortable_dir_entry, 0, cap(arena)) + } + arena = arena[:len(entries)] + sortable = sortable[:len(entries)] + for i, e := range entries { + arena[i].name = e.Name() + ftype := e.Type() + if ftype&fs.ModeSymlink != 0 { + if st, serr := os.Stat(dir + arena[i].name); serr == nil && st.IsDir() { + ftype |= SymlinkToDir } - entries = nil } - if cap(arena) < len(entries) { - arena = make([]sortable_dir_entry, 0, max(1024, len(entries), 2*cap(arena))) - sortable = make([]*sortable_dir_entry, 0, cap(arena)) + arena[i].ftype = ftype + if ftype&fs.ModeDir != 0 { + arena[i].buf[0] = '0' + } else { + arena[i].buf[0] = '1' } - arena = arena[:len(entries)] - sortable = sortable[:len(entries)] - for i, e := range entries { - arena[i].name = e.Name() - ftype := e.Type() - if ftype&fs.ModeSymlink != 0 { - if st, serr := os.Stat(dir + arena[i].name); serr == nil && st.IsDir() { - ftype = fs.ModeDir | 1 // 1 indicates was originally a symlink - symlink_dir_map[dir+arena[i].name] = st.Name() - } - } - arena[i].ftype = ftype - if ftype&fs.ModeDir != 0 { - arena[i].buf[0] = '0' - } else { - arena[i].buf[0] = '1' - } - n := as_lower(arena[i].name, arena[i].buf[1:]) - arena[i].sort_key = arena[i].buf[:1+n] - sortable[i] = &arena[i] - } - slices.SortFunc(sortable, func(a, b *sortable_dir_entry) int { return bytes.Compare(a.sort_key, b.sort_key) }) - ns := fss.results - new_sz := len(ns) + len(entries) - if cap(ns) < new_sz { - ns = make([]ResultItem, len(ns), max(1024, new_sz, cap(ns)*2)) - copy(ns, fss.results) - } - new_items := ns[len(ns):new_sz] - for i, e := range sortable { - new_items[i].ftype = e.ftype - new_items[i].abspath = dir + e.name - new_items[i].text = base + e.name - new_items[i].score.Set_index(idx) - idx++ - } - ns = ns[0:new_sz] - fss.lock() - fss.results = ns - listeners := fss.listeners - fss.unlock() - for _, l := range listeners { - select { - case l <- true: - default: - } + n := as_lower(arena[i].name, arena[i].buf[1:]) + arena[i].sort_key = arena[i].buf[:1+n] + sortable[i] = &arena[i] + } + slices.SortFunc(sortable, func(a, b *sortable_dir_entry) int { return bytes.Compare(a.sort_key, b.sort_key) }) + ns := fss.results + new_sz := len(ns) + len(entries) + if cap(ns) < new_sz { + ns = make([]ResultItem, len(ns), max(1024, new_sz, cap(ns)*2)) + copy(ns, fss.results) + } + new_items := ns[len(ns):new_sz] + for i, e := range sortable { + new_items[i].ftype = e.ftype + new_items[i].abspath = dir + e.name + new_items[i].text = base + e.name + new_items[i].score.Set_index(idx) + idx++ + } + ns = ns[0:new_sz] + fss.lock() + fss.results = ns + listeners := fss.listeners + fss.unlock() + for _, l := range listeners { + select { + case l <- true: + default: } } dir = "" @@ -288,6 +285,7 @@ func (fss *FileSystemScanner) worker() { } pos++ } + is_root = false } }