ronantakizawa/codereview-bench huggingface dataset:
Repository: syncthing/syncthing
File: lib/model/folder.go (Go)

Code before:
	f.scanErrorsMut.Lock()
	defer f.scanErrorsMut.Unlock()
	if len(subDirs) == 0 {
		f.scanErrors = nil
		return
	}
	filtered := f.scanErrors[:0]
outer:
	for _, fe := range f.scanErrors {
		for _, sub := range subDirs {
			if fe.Path == sub || fs.IsParent(fe.Path, sub) {
				continue outer
			}
		}
		filtered = append(filtered, fe)
	}
	f.scanErrors = filtered
}

func (f *folder) Errors() []FileError {
	f.scanErrorsMut.Lock()
	defer f.scanErrorsMut.Unlock()
	return append([]FileError{}, f.scanErrors...)
}

// ScheduleForceRescan marks the file such that it gets rehashed on next scan, and schedules a scan when possible.
func (f *folder) ScheduleForceRescan(path string) {
	f.forcedRescanPathsMut.Lock()
	f.forcedRescanPaths[path] = struct{}{}
	f.forcedRescanPathsMut.Unlock()

	select {
	case f.forcedRescanRequested <- struct{}{}:
	default:
	}
}

func (f *folder) updateLocalsFromScanning(fs []protocol.FileInfo) {
	f.updateLocals(fs)

	f.emitDiskChangeEvents(fs, events.LocalChangeDetected)
}

func (f *folder) updateLocalsFromPulling(fs []protocol.FileInfo) {
	f.updateLocals(fs)

	f.emitDiskChangeEvents(fs, events.RemoteChangeDetected)
}

func (f *folder) updateLocals(fs []protocol.FileInfo) {
	f.fset.Update(protocol.LocalDeviceID, fs)

Review comment (suggestion):
```suggestion
// ScheduleForceRescan marks the file such that it gets rehashed on next scan, and schedules a scan.
```

The scan is scheduled regardless, and that's what counts.

Code after:
	f.scanErrorsMut.Lock()
	defer f.scanErrorsMut.Unlock()
	if len(subDirs) == 0 {
		f.scanErrors = nil
		return
	}
	filtered := f.scanErrors[:0]
outer:
	for _, fe := range f.scanErrors {
		for _, sub := range subDirs {
			if fe.Path == sub || fs.IsParent(fe.Path, sub) {
				continue outer
			}
		}
		filtered = append(filtered, fe)
	}
	f.scanErrors = filtered
}

func (f *folder) Errors() []FileError {
	f.scanErrorsMut.Lock()
	defer f.scanErrorsMut.Unlock()
	return append([]FileError{}, f.scanErrors...)
}

// ScheduleForceRescan marks the file such that it gets rehashed on next scan, and schedules a scan.
func (f *folder) ScheduleForceRescan(path string) {
	f.forcedRescanPathsMut.Lock()
	f.forcedRescanPaths[path] = struct{}{}
	f.forcedRescanPathsMut.Unlock()

	select {
	case f.forcedRescanRequested <- struct{}{}:
	default:
	}
}

func (f *folder) updateLocalsFromScanning(fs []protocol.FileInfo) {
	f.updateLocals(fs)

	f.emitDiskChangeEvents(fs, events.LocalChangeDetected)
}

func (f *folder) updateLocalsFromPulling(fs []protocol.FileInfo) {
	f.updateLocals(fs)

	f.emitDiskChangeEvents(fs, events.RemoteChangeDetected)
}

func (f *folder) updateLocals(fs []protocol.FileInfo) {
	f.fset.Update(protocol.LocalDeviceID, fs)
Repository: syncthing/syncthing
File: lib/model/folder.go (Go)

Code before:
	f.forcedRescanPaths = make(map[string]struct{})
	f.forcedRescanPathsMut.Unlock()

	batch := newFileInfoBatch(func(fs []protocol.FileInfo) error {
		f.fset.Update(protocol.LocalDeviceID, fs)
		return nil
	})

	snap := f.fset.Snapshot()
	defer snap.Release()

	for _, path := range paths {
		_ = batch.flushIfFull()

		fi, ok := snap.Get(protocol.LocalDeviceID, path)
		if !ok {
			continue
		}
		fi.SetMustRescan(f.shortID)
		batch.append(fi)
	}

	_ = batch.flush()

	_ = f.scanSubdirs(paths)
}

// The exists function is expected to return true for all known paths
// (excluding "" and ".")
func unifySubs(dirs []string, exists func(dir string) bool) []string {
	if len(dirs) == 0 {
		return nil
	}
	sort.Strings(dirs)
	if dirs[0] == "" || dirs[0] == "." || dirs[0] == string(fs.PathSeparator) {
		return nil
	}
	prev := "./" // Anything that can't be parent of a clean path
	for i := 0; i < len(dirs); {
		dir, err := fs.Canonicalize(dirs[i])
		if err != nil {
			l.Debugf("Skipping %v for scan: %s", dirs[i], err)
			dirs = append(dirs[:i], dirs[i+1:]...)
			continue
		}
		if dir == prev || fs.IsParent(dir, prev) {
			dirs = append(dirs[:i], dirs[i+1:]...)
			continue
		}
		parent := filepath.Dir(dir)
		for parent != "." && parent != string(fs.PathSeparator) && !exists(parent) {

Review comment (performance):
I'd release the batch before scanning. Maybe the memory footprint of a read-only transaction is entirely irrelevant, but still no harm done.

Code after:
	f.forcedRescanPaths = make(map[string]struct{})
	f.forcedRescanPathsMut.Unlock()

	batch := newFileInfoBatch(func(fs []protocol.FileInfo) error {
		f.fset.Update(protocol.LocalDeviceID, fs)
		return nil
	})

	snap := f.fset.Snapshot()

	for _, path := range paths {
		_ = batch.flushIfFull()

		fi, ok := snap.Get(protocol.LocalDeviceID, path)
		if !ok {
			continue
		}
		fi.SetMustRescan(f.shortID)
		batch.append(fi)
	}

	snap.Release()

	_ = batch.flush()

	_ = f.scanSubdirs(paths)
}

// The exists function is expected to return true for all known paths
// (excluding "" and ".")
func unifySubs(dirs []string, exists func(dir string) bool) []string {
	if len(dirs) == 0 {
		return nil
	}
	sort.Strings(dirs)
	if dirs[0] == "" || dirs[0] == "." || dirs[0] == string(fs.PathSeparator) {
		return nil
	}
	prev := "./" // Anything that can't be parent of a clean path
	for i := 0; i < len(dirs); {
		dir, err := fs.Canonicalize(dirs[i])
		if err != nil {
			l.Debugf("Skipping %v for scan: %s", dirs[i], err)
			dirs = append(dirs[:i], dirs[i+1:]...)
			continue
		}
		if dir == prev || fs.IsParent(dir, prev) {
			dirs = append(dirs[:i], dirs[i+1:]...)
			continue
		}
		parent := filepath.Dir(dir)
Repository: syncthing/syncthing
File: lib/model/blockpullreorderer.go (Go)

Code before:

import (
	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/protocol"
	"github.com/syncthing/syncthing/lib/rand"
	"sort"
)

type blockPullReorderer interface {
	Reorder(blocks []protocol.BlockInfo) []protocol.BlockInfo
}

type inOrderBlockPullReorderer struct{}

func (inOrderBlockPullReorderer) Reorder(blocks []protocol.BlockInfo) []protocol.BlockInfo {
	return blocks
}

type randomOrderBlockPullReorderer struct{}

func (randomOrderBlockPullReorderer) Reorder(blocks []protocol.BlockInfo) []protocol.BlockInfo {
	rand.Shuffle(blocks)
	return blocks
}

func newBlockPullReorderer(order config.BlockPullOrder, id protocol.DeviceID, otherDevices []protocol.DeviceID) blockPullReorderer {
	switch order {
	case config.BlockPullOrderRandom:
		return randomOrderBlockPullReorderer{}
	case config.BlockPullOrderInOrder:
		return inOrderBlockPullReorderer{}
	case config.BlockPullOrderStandard:
		fallthrough
	default:
		return newStandardBlockPullReorderer(id, otherDevices)
	}
}

func newStandardBlockPullReorderer(id protocol.DeviceID, otherDevices []protocol.DeviceID) *standardBlockPullReorderer {
	allDevices := append(otherDevices, id)
	sort.Slice(allDevices, func(i, j int) bool {
		return allDevices[i].Compare(allDevices[j]) == -1
	})
	// Find our index
	myIndex := -1
	for i, dev := range allDevices {
		if dev == id {
			myIndex = i
			break
		}
	}

Review comment (suggestion):
I'd move that to directly below the `blockPullReorder` interface. 

Code after:

import (
	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/protocol"
	"github.com/syncthing/syncthing/lib/rand"
	"sort"
)

type blockPullReorderer interface {
	Reorder(blocks []protocol.BlockInfo) []protocol.BlockInfo
}

func newBlockPullReorderer(order config.BlockPullOrder, id protocol.DeviceID, otherDevices []protocol.DeviceID) blockPullReorderer {
	switch order {
	case config.BlockPullOrderRandom:
		return randomOrderBlockPullReorderer{}
	case config.BlockPullOrderInOrder:
		return inOrderBlockPullReorderer{}
	case config.BlockPullOrderStandard:
		fallthrough
	default:
		return newStandardBlockPullReorderer(id, otherDevices)
	}
}

type inOrderBlockPullReorderer struct{}

func (inOrderBlockPullReorderer) Reorder(blocks []protocol.BlockInfo) []protocol.BlockInfo {
	return blocks
}

type randomOrderBlockPullReorderer struct{}

func (randomOrderBlockPullReorderer) Reorder(blocks []protocol.BlockInfo) []protocol.BlockInfo {
	rand.Shuffle(blocks)
	return blocks
}

type standardBlockPullReorderer struct {
	myIndex int
	count   int
	shuffle func(interface{}) // Used for test
}

func newStandardBlockPullReorderer(id protocol.DeviceID, otherDevices []protocol.DeviceID) *standardBlockPullReorderer {
	allDevices := append(otherDevices, id)
	sort.Slice(allDevices, func(i, j int) bool {
		return allDevices[i].Compare(allDevices[j]) == -1
	})
	// Find our index
	myIndex := -1
Repository: syncthing/syncthing
File: lib/model/folder_sendrecv.go (Go)

Code before:
func (f *sendReceiveFolder) BringToFront(filename string) {
	f.queue.BringToFront(filename)
}

func (f *sendReceiveFolder) Jobs(page, perpage int) ([]string, []string, int) {
	return f.queue.Jobs(page, perpage)
}

// dbUpdaterRoutine aggregates db updates and commits them in batches no
// larger than 1000 items, and no more delayed than 2 seconds.
func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) {
	const maxBatchTime = 2 * time.Second

	batch := newFileInfoBatch(nil)
	tick := time.NewTicker(maxBatchTime)
	defer tick.Stop()

	changedDirs := make(map[string]struct{})
	found := false
	var lastFile protocol.FileInfo

	batch.flushFn = func(files []protocol.FileInfo) error {
		// sync directories
		for dir := range changedDirs {
			delete(changedDirs, dir)
			fd, err := f.fs.Open(dir)
			if err != nil {
				l.Debugf("fsync %q failed: %v", dir, err)
				continue
			}
			if !f.FolderConfiguration.DisableFsync {
				if err := fd.Sync(); err != nil {
					l.Debugf("fsync %q failed: %v", dir, err)
				}
			}
			fd.Close()
		}

		// All updates to file/folder objects that originated remotely
		// (across the network) use this call to updateLocals
		f.updateLocalsFromPulling(files)

		if found {
			f.ReceivedFile(lastFile.Name, lastFile.IsDeleted())
			found = false
		}

		return nil
	}

loop:

Review comment (suggestion):
The entire flushfn shoudl go away if we're not syncing, and ideally we don't need to track changedDir either (but the cost isn't enormous so it might not be worth sprinkling yet more ifs to accomplish it, I didn't check)

Code after:
func (f *sendReceiveFolder) BringToFront(filename string) {
	f.queue.BringToFront(filename)
}

func (f *sendReceiveFolder) Jobs(page, perpage int) ([]string, []string, int) {
	return f.queue.Jobs(page, perpage)
}

// dbUpdaterRoutine aggregates db updates and commits them in batches no
// larger than 1000 items, and no more delayed than 2 seconds.
func (f *sendReceiveFolder) dbUpdaterRoutine(dbUpdateChan <-chan dbUpdateJob) {
	const maxBatchTime = 2 * time.Second

	batch := newFileInfoBatch(nil)
	tick := time.NewTicker(maxBatchTime)
	defer tick.Stop()

	changedDirs := make(map[string]struct{})
	found := false
	var lastFile protocol.FileInfo

	batch.flushFn = func(files []protocol.FileInfo) error {
		// sync directories
		for dir := range changedDirs {
			delete(changedDirs, dir)
			if !f.FolderConfiguration.DisableFsync {
				fd, err := f.fs.Open(dir)
				if err != nil {
					l.Debugf("fsync %q failed: %v", dir, err)
					continue
				}
				if err := fd.Sync(); err != nil {
					l.Debugf("fsync %q failed: %v", dir, err)
				}
				fd.Close()
			}
		}

		// All updates to file/folder objects that originated remotely
		// (across the network) use this call to updateLocals
		f.updateLocalsFromPulling(files)

		if found {
			f.ReceivedFile(lastFile.Name, lastFile.IsDeleted())
			found = false
		}

		return nil
	}

loop:
Repository: syncthing/syncthing
File: lib/connections/structs.go (Go)

Code before:

type Model interface {
	protocol.Model
	AddConnection(conn Connection, hello protocol.HelloResult)
	Connection(remoteID protocol.DeviceID) (Connection, bool)
	OnHello(protocol.DeviceID, net.Addr, protocol.HelloResult) error
	GetHello(protocol.DeviceID) protocol.HelloIntf
}

type onAddressesChangedNotifier struct {
	callbacks []func(ListenerAddresses)
}

func (o *onAddressesChangedNotifier) OnAddressesChanged(callback func(ListenerAddresses)) {
	o.callbacks = append(o.callbacks, callback)
}

func (o *onAddressesChangedNotifier) notifyAddressesChanged(l genericListener) {
	o.notifyAddresses(ListenerAddresses{
		URI:          l.URI(),
		WANAddresses: l.WANAddresses(),
		LANAddresses: l.LANAddresses(),
	})
}

func (o *onAddressesChangedNotifier) clearAddresses(uri *url.URL) {
	o.notifyAddresses(ListenerAddresses{
		URI: uri,
	})
}

func (o *onAddressesChangedNotifier) notifyAddresses(l ListenerAddresses) {
	for _, callback := range o.callbacks {
		callback(l)
	}
}

type dialTarget struct {
	addr     string
	dialer   genericDialer
	priority int
	uri      *url.URL
	deviceID protocol.DeviceID
}

func (t dialTarget) Dial(ctx context.Context) (internalConn, error) {
	l.Debugln("dialing", t.deviceID, t.uri, "prio", t.priority)
	return t.dialer.Dial(ctx, t.deviceID, t.uri)
}


Review comment (suggestion):
I'd have this one take a `genericListener` as well and call `URI()` on it just for symmetry with `notifyAddressesChanged` and making sure we clear the same URI we've announced

Code after:

type Model interface {
	protocol.Model
	AddConnection(conn Connection, hello protocol.HelloResult)
	Connection(remoteID protocol.DeviceID) (Connection, bool)
	OnHello(protocol.DeviceID, net.Addr, protocol.HelloResult) error
	GetHello(protocol.DeviceID) protocol.HelloIntf
}

type onAddressesChangedNotifier struct {
	callbacks []func(ListenerAddresses)
}

func (o *onAddressesChangedNotifier) OnAddressesChanged(callback func(ListenerAddresses)) {
	o.callbacks = append(o.callbacks, callback)
}

func (o *onAddressesChangedNotifier) notifyAddressesChanged(l genericListener) {
	o.notifyAddresses(ListenerAddresses{
		URI:          l.URI(),
		WANAddresses: l.WANAddresses(),
		LANAddresses: l.LANAddresses(),
	})
}

func (o *onAddressesChangedNotifier) clearAddresses(l genericListener) {
	o.notifyAddresses(ListenerAddresses{
		URI: l.URI(),
	})
}

func (o *onAddressesChangedNotifier) notifyAddresses(l ListenerAddresses) {
	for _, callback := range o.callbacks {
		callback(l)
	}
}

type dialTarget struct {
	addr     string
	dialer   genericDialer
	priority int
	uri      *url.URL
	deviceID protocol.DeviceID
}

func (t dialTarget) Dial(ctx context.Context) (internalConn, error) {
	l.Debugln("dialing", t.deviceID, t.uri, "prio", t.priority)
	return t.dialer.Dial(ctx, t.deviceID, t.uri)
}
Repository: syncthing/syncthing
File: lib/connections/quic_dial.go (Go)

Code before:
	if listenConn := registry.Get(uri.Scheme, packetConnLess); listenConn != nil {
		conn = listenConn.(net.PacketConn)
	} else {
		if packetConn, err := net.ListenPacket("udp", ":0"); err != nil {
			return internalConn{}, err
		} else {
			conn = packetConn
			createdConn = packetConn
		}
	}

	ctx, cancel := context.WithTimeout(ctx, quicOperationTimeout)
	defer cancel()

	session, err := quic.DialContext(ctx, conn, addr, uri.Host, d.tlsCfg, quicConfig)
	if err != nil {
		if createdConn != nil {
			_ = createdConn.Close()
		}
		return internalConn{}, errors.Wrap(err, "dial")
	}

	stream, err := session.OpenStreamSync(ctx)
	if err != nil {
		// It's ok to close these, this does not close the underlying packetConn.
		_ = session.CloseWithError(0, err.Error())
		if createdConn != nil {
			_ = createdConn.Close()
		}
		return internalConn{}, errors.Wrap(err, "open stream")
	}

	return internalConn{&quicTlsConn{session, stream, createdConn}, connTypeQUICClient, quicPriority}, nil
}

type quicDialerFactory struct {
	cfg    config.Wrapper
	tlsCfg *tls.Config
}

func (quicDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config) genericDialer {
	return &quicDialer{commonDialer{
		reconnectInterval: time.Duration(opts.ReconnectIntervalS) * time.Second,
		tlsCfg:            tlsCfg,
	}}
}

func (quicDialerFactory) Priority() int {
	return quicPriority
}


Review comment (suggestion):
Probably doesn't matter, but I'd expect `1` for the generic BEP error (or just any generic error)? Same below.

Code after:
	if listenConn := registry.Get(uri.Scheme, packetConnLess); listenConn != nil {
		conn = listenConn.(net.PacketConn)
	} else {
		if packetConn, err := net.ListenPacket("udp", ":0"); err != nil {
			return internalConn{}, err
		} else {
			conn = packetConn
			createdConn = packetConn
		}
	}

	ctx, cancel := context.WithTimeout(ctx, quicOperationTimeout)
	defer cancel()

	session, err := quic.DialContext(ctx, conn, addr, uri.Host, d.tlsCfg, quicConfig)
	if err != nil {
		if createdConn != nil {
			_ = createdConn.Close()
		}
		return internalConn{}, errors.Wrap(err, "dial")
	}

	stream, err := session.OpenStreamSync(ctx)
	if err != nil {
		// It's ok to close these, this does not close the underlying packetConn.
		_ = session.CloseWithError(1, err.Error())
		if createdConn != nil {
			_ = createdConn.Close()
		}
		return internalConn{}, errors.Wrap(err, "open stream")
	}

	return internalConn{&quicTlsConn{session, stream, createdConn}, connTypeQUICClient, quicPriority}, nil
}

type quicDialerFactory struct {
	cfg    config.Wrapper
	tlsCfg *tls.Config
}

func (quicDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config) genericDialer {
	return &quicDialer{commonDialer{
		reconnectInterval: time.Duration(opts.ReconnectIntervalS) * time.Second,
		tlsCfg:            tlsCfg,
	}}
}

func (quicDialerFactory) Priority() int {
	return quicPriority
}
Repository: syncthing/syncthing
File: lib/fs/walkfs.go (Go)

Code before:
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This part copied directly from golang.org/src/path/filepath/path.go (Go
// 1.6) and lightly modified to be methods on BasicFilesystem.

// In our Walk() all paths given to a WalkFunc() are relative to the
// filesystem root.

package fs

import (
	"fmt"
	"path/filepath"
)

func newAncestorDirList() *ancestorDirList {
	l.Debugf("ancestorDirList: new")
	return &ancestorDirList{list: make([]FileInfo, 0, 20)}
}

type ancestorDirList struct {
	list []FileInfo
}

func (ancestors *ancestorDirList) AppendUnlessPresent(info FileInfo) bool {
	l.Debugf("ancestorDirList: AppendUnlessPresent '%s'", info.Name())
	for _, ancestor := range ancestors.list {
		if SameFile(info, ancestor) {
			return false
		}
	}
	aLen := len(ancestors.list)
	if aLen == cap(ancestors.list) {
		t := make([]FileInfo, aLen, aLen*2)
		copy(t, ancestors.list)
		ancestors.list = t
		l.Debugf("ancestorDirList: capacity increased to %d", cap(ancestors.list))
	}
	ancestors.list = append(ancestors.list, info)
	return true
}

func (ancestors *ancestorDirList) Remove(info FileInfo) bool {

Review comment (performance):
No need to preallocate the ancestor list here, in fact probably no need for the constructor, `var foo ancestorDirList` initializes this enough.

Code after:
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This part copied directly from golang.org/src/path/filepath/path.go (Go
// 1.6) and lightly modified to be methods on BasicFilesystem.

// In our Walk() all paths given to a WalkFunc() are relative to the
// filesystem root.

package fs

import (
	"path/filepath"
)

type ancestorDirList struct {
	list []FileInfo
	fs   Filesystem
}

func (ancestors *ancestorDirList) Push(info FileInfo) {
	l.Debugf("ancestorDirList: Push '%s'", info.Name())
	ancestors.list = append(ancestors.list, info)
}

func (ancestors *ancestorDirList) Pop() FileInfo {
	aLen := len(ancestors.list)
	info := ancestors.list[aLen-1]
	l.Debugf("ancestorDirList: Pop '%s'", info.Name())
	ancestors.list = ancestors.list[:aLen-1]
	return info
}

func (ancestors *ancestorDirList) Contains(info FileInfo) bool {
	l.Debugf("ancestorDirList: Contains '%s'", info.Name())
	for _, ancestor := range ancestors.list {
		if ancestors.fs.SameFile(info, ancestor) {
			return true
		}
	}
	return false
}

// WalkFunc is the type of the function called for each file or directory
Repository: syncthing/syncthing
File: lib/fs/basicfs_fileinfo_windows.go (Go)

Code before:
		m |= 0111
	}
	// There is no user/group/others in Windows' read-only attribute, and
	// all "w" bits are set if the file is not read-only.  Do not send these
	// group/others-writable bits to other devices in order to avoid
	// unexpected world-writable files on other platforms.
	m &^= 0022
	return FileMode(m)
}

func (e basicFileInfo) Owner() int {
	return -1
}

func (e basicFileInfo) Group() int {
	return -1
}

// fileStat converts e to os.FileInfo that is suitable
// to be passed to os.SameFile.
func (e *basicFileInfo) fileStat() os.FileInfo {
	fi := e.FileInfo
	fi2, ok2 := fi.(*dirJunctFileInfo)
	if ok2 {
		fi = fi2.FileInfo
	}
	return fi
}


Review comment (suggestion):
```suggestion
	if fi, ok := fi.(*dirJunctFileInfo); ok {
	    return fi
	}
```

avoids the extra temp vars

Code after:
		m |= 0111
	}
	// There is no user/group/others in Windows' read-only attribute, and
	// all "w" bits are set if the file is not read-only.  Do not send these
	// group/others-writable bits to other devices in order to avoid
	// unexpected world-writable files on other platforms.
	m &^= 0022
	return FileMode(m)
}

func (e basicFileInfo) Owner() int {
	return -1
}

func (e basicFileInfo) Group() int {
	return -1
}

// osFileInfo converts e to os.FileInfo that is suitable
// to be passed to os.SameFile.
func (e *basicFileInfo) osFileInfo() os.FileInfo {
	fi := e.FileInfo
	if fi, ok := fi.(*dirJunctFileInfo); ok {
		return fi.FileInfo
	}
	return fi
}