<commit_before>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libfs

import (
	"bytes"
	"context"
	"crypto/rand"
	"encoding/base64"
	"os"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/keybase/client/go/logger"
	"github.com/keybase/kbfs/libkbfs"
	"github.com/pkg/errors"
	billy "gopkg.in/src-d/go-billy.v3"
)

// FSEventType is FS event type.
type FSEventType int

const (
	_ FSEventType = iota
	// FSEventLock indicates Lock method has been called.
	FSEventLock
	// FSEventUnlock indicates Unlock method has been called.
	FSEventUnlock
)

// FSEvent is the type for events sent into the events channel passed into
// NewFS.
type FSEvent struct {
	EventType FSEventType
	File      *File
	Done      <-chan struct{}
}

// FS is a wrapper around a KBFS subdirectory that implements the
// billy.Filesystem interface.  It uses forward-slash separated paths.
// It may return errors wrapped with the `github.com/pkg/errors`
// package.
type FS struct {
	// Yes, storing ctx in a struct is a mortal sin, but the
	// billy.Filesystem interface doesn't give us a way to accept ctxs
	// any other way.
	ctx      context.Context
	config   libkbfs.Config
	root     libkbfs.Node
	rootInfo libkbfs.EntryInfo
	h        *libkbfs.TlfHandle
	subdir   string
	uniqID   string
	log      logger.Logger
	deferLog logger.Logger
	// lockNamespace is the prefix used by any *File generated by this *FS when
	// they need to generate a lockID. By default, we use a canonical unix path
	// of the root of this FS as lock namespace. But one can call
	// SetLockNamespace to set it explicitly, which can be any bytes. When
	// Chroot is called, a slash ('/') followed by the changed subpath are
	// appended to the existing lockNamespace to form the new one. Note that
	// this is a naive append without and path clean.
	lockNamespace []byte

	eventsLock sync.RWMutex
	events     map[chan<- FSEvent]bool
}

var _ billy.Filesystem = (*FS)(nil)

const (
	maxSymlinkLevels = 40 // same as Linux
)

func followSymlink(parentPath, link string) (newPath string, err error) {
	if path.IsAbs(link) {
		return "", errors.Errorf("Can't follow absolute link: %s", link)
	}

	newPath = path.Clean(path.Join(parentPath, link))
	if strings.HasPrefix(newPath, "..") {
		return "", errors.Errorf(
			"Cannot follow symlink out of chroot: %s", newPath)
	}

	return newPath, nil
}

// NewFS returns a new FS instance, chroot'd to the given TLF and
// subdir within that TLF.  `subdir` must exist, and point to a
// directory, before this function is called.  `uniqID` needs to
// uniquely identify this instance among all users of this TLF
// globally; for example, a device ID combined with a local tempfile
// name is recommended.
func NewFS(ctx context.Context, config libkbfs.Config,
	tlfHandle *libkbfs.TlfHandle, subdir string, uniqID string) (*FS, error) {
	rootNode, ei, err := config.KBFSOps().GetOrCreateRootNode(
		ctx, tlfHandle, libkbfs.MasterBranch)
	if err != nil {
		return nil, err
	}

	// Look up the subdir's root.
	n := rootNode
	var parts []string
	if len(subdir) > 0 {
		parts = strings.Split(subdir, "/")
	}
	// Loop while we follow symlinks.
outer:
	for {
		for i, p := range parts {
			n, ei, err = config.KBFSOps().Lookup(ctx, n, p)
			if err != nil {
				return nil, err
			}
			switch ei.Type {
			case libkbfs.Dir:
				continue
			case libkbfs.Sym:
				parentParts := parts[:i]<commit_msg>libfs: clean subdir in `NewFS` if needed<commit_after>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libfs

import (
	"bytes"
	"context"
	"crypto/rand"
	"encoding/base64"
	"os"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/keybase/client/go/logger"
	"github.com/keybase/kbfs/libkbfs"
	"github.com/pkg/errors"
	billy "gopkg.in/src-d/go-billy.v3"
)

// FSEventType is FS event type.
type FSEventType int

const (
	_ FSEventType = iota
	// FSEventLock indicates Lock method has been called.
	FSEventLock
	// FSEventUnlock indicates Unlock method has been called.
	FSEventUnlock
)

// FSEvent is the type for events sent into the events channel passed into
// NewFS.
type FSEvent struct {
	EventType FSEventType
	File      *File
	Done      <-chan struct{}
}

// FS is a wrapper around a KBFS subdirectory that implements the
// billy.Filesystem interface.  It uses forward-slash separated paths.
// It may return errors wrapped with the `github.com/pkg/errors`
// package.
type FS struct {
	// Yes, storing ctx in a struct is a mortal sin, but the
	// billy.Filesystem interface doesn't give us a way to accept ctxs
	// any other way.
	ctx      context.Context
	config   libkbfs.Config
	root     libkbfs.Node
	rootInfo libkbfs.EntryInfo
	h        *libkbfs.TlfHandle
	subdir   string
	uniqID   string
	log      logger.Logger
	deferLog logger.Logger
	// lockNamespace is the prefix used by any *File generated by this *FS when
	// they need to generate a lockID. By default, we use a canonical unix path
	// of the root of this FS as lock namespace. But one can call
	// SetLockNamespace to set it explicitly, which can be any bytes. When
	// Chroot is called, a slash ('/') followed by the changed subpath are
	// appended to the existing lockNamespace to form the new one. Note that
	// this is a naive append without and path clean.
	lockNamespace []byte

	eventsLock sync.RWMutex
	events     map[chan<- FSEvent]bool
}

var _ billy.Filesystem = (*FS)(nil)

const (
	maxSymlinkLevels = 40 // same as Linux
)

func followSymlink(parentPath, link string) (newPath string, err error) {
	if path.IsAbs(link) {
		return "", errors.Errorf("Can't follow absolute link: %s", link)
	}

	newPath = path.Clean(path.Join(parentPath, link))
	if strings.HasPrefix(newPath, "..") {
		return "", errors.Errorf(
			"Cannot follow symlink out of chroot: %s", newPath)
	}

	return newPath, nil
}

// NewFS returns a new FS instance, chroot'd to the given TLF and
// subdir within that TLF.  `subdir` must exist, and point to a
// directory, before this function is called.  `uniqID` needs to
// uniquely identify this instance among all users of this TLF
// globally; for example, a device ID combined with a local tempfile
// name is recommended.
func NewFS(ctx context.Context, config libkbfs.Config,
	tlfHandle *libkbfs.TlfHandle, subdir string, uniqID string) (*FS, error) {
	rootNode, ei, err := config.KBFSOps().GetOrCreateRootNode(
		ctx, tlfHandle, libkbfs.MasterBranch)
	if err != nil {
		return nil, err
	}
	if subdir != "" {
		subdir = path.Clean(subdir)
	}

	// Look up the subdir's root.
	n := rootNode
	var parts []string
	if len(subdir) > 0 {
		parts = strings.Split(subdir, "/")
	}
	// Loop while we follow symlinks.
outer:
	for {
		for i, p := range parts {
			n, ei, err = config.KBFSOps().Lookup(ctx, n, p)
			if err != nil {
				return nil, err
			}
			switch ei.Type {
			case libkbfs.Dir:
				continue
			case libkbfs.Sym:
				parentParts := parts[:i]
{'fraction_non_alphanumeric': 0.09856757906679904, 'fraction_numerical': 0.002836477095447454, 'mean_word_length': 3.2765312310491206, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2673118', 'n_tokens_mistral': 2474, 'n_tokens_neox': 2338, 'n_words': 875}