<commit_before>// Copyright 2016 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 libfuse

import (
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/kbfs/libkbfs"
	"github.com/keybase/kbfs/tlf"
	"golang.org/x/net/context"
)

// FolderList is a node that can list all of the logged-in user's
// favorite top-level folders, on either a public or private basis.
type FolderList struct {
	fs *FS
	// only accept public folders
	tlfType tlf.Type

	mu      sync.Mutex
	folders map[string]*TLF

	muRecentlyRemoved sync.RWMutex
	recentlyRemoved   map[libkbfs.CanonicalTlfName]bool
}

var _ fs.NodeAccesser = (*FolderList)(nil)

// Access implements fs.NodeAccesser interface for *FolderList.
func (*FolderList) Access(ctx context.Context, r *fuse.AccessRequest) error {
	if int(r.Uid) != os.Getuid() &&
		// Finder likes to use UID 0 for some operations. osxfuse already allows
		// ACCESS and GETXATTR requests from root to go through. This allows root
		// in ACCESS handler. See KBFS-1733 for more details.
		int(r.Uid) != 0 {
		// short path: not accessible by anybody other than root or the user who
		// executed the kbfsfuse process.
		return fuse.EPERM
	}

	if r.Mask&02 != 0 {
		return fuse.EPERM
	}

	return nil
}

var _ fs.Node = (*FolderList)(nil)

// Attr implements the fs.Node interface.
func (*FolderList) Attr(ctx context.Context, a *fuse.Attr) error {
	a.Mode = os.ModeDir | 0500
	a.Uid = uint32(os.Getuid())
	return nil
}

var _ fs.NodeRequestLookuper = (*FolderList)(nil)

func (fl *FolderList) reportErr(ctx context.Context,
	mode libkbfs.ErrorModeType, tlfName libkbfs.CanonicalTlfName, err error) {
	if err == nil {
		fl.fs.errLog.CDebugf(ctx, "Request complete")
		return
	}

	fl.fs.config.Reporter().ReportErr(ctx, tlfName, fl.tlfType, mode, err)
	// We just log the error as debug, rather than error, because it
	// might just indicate an expected error such as an ENOENT.
	//
	// TODO: Classify errors and escalate the logging level of the
	// important ones.
	fl.fs.errLog.CDebugf(ctx, err.Error())
}

func (fl *FolderList) addToRecentlyRemoved(name libkbfs.CanonicalTlfName) {
	func() {
		fl.muRecentlyRemoved.Lock()
		defer fl.muRecentlyRemoved.Unlock()
		if fl.recentlyRemoved == nil {
			fl.recentlyRemoved = make(map[libkbfs.CanonicalTlfName]bool)
		}
		fl.recentlyRemoved[name] = true
	}()
	fl.fs.execAfterDelay(time.Second, func() {
		fl.muRecentlyRemoved.Lock()
		defer fl.muRecentlyRemoved.Unlock()
		delete(fl.recentlyRemoved, name)
	})
}

func (fl *FolderList) isRecentlyRemoved(name libkbfs.CanonicalTlfName) bool {
	fl.muRecentlyRemoved.RLock()
	defer fl.muRecentlyRemoved.RUnlock()
	return fl.recentlyRemoved != nil && fl.recentlyRemoved[name]
}

func (fl *FolderList) addToFavorite(ctx context.Context, h *libkbfs.TlfHandle) (err error) {
	cName := h.GetCanonicalName()

	// `rmdir` command on macOS does a lookup after removing the dir. if the
	// TLF is recently removed, it's likely that this lookup is issued by the
	// `rmdir` command, and the lookup should not result in adding the dir to
	// favorites.
	if !fl.isRecentlyRemoved(cName) {
		fl.fs.log.CDebugf(ctx, "adding %s to favorites", cName)
		fl.fs.config.KBFSOps().AddFavorite(ctx, h.ToFavorite())
	} else {
		fl.fs.log.CDebugf(ctx, "recently removed; will skip adding %s to favorites and return ENOENT", cName)
		return fuse.ENOENT
	}
	return nil
}

// PathType returns PathType for this folder
func (fl *FolderList) PathType() libkbfs.PathType {
	switch fl.tlfType {
	case tlf.Private:
		return libkbfs.PrivatePathType
	case tlf.Public:
		return libkbfs.PublicPathType
	default:
		// TODO: support the team path.
		panic(fmt.Sprintf("Unsupported tlf type: %s", fl.tlfType))
	}
}

// Create implements the fs.NodeCreater interface for FolderList.
func (fl *FolderList) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (_ fs.Node, _ fs.Handle, err error) {
	fl.fs.log.CDebugf(ctx, "FL Create")
	tlfName := libkbfs.CanonicalTlfName(req.Name)
	defer func() { fl.reportErr(ctx, libkbfs.WriteMode, tlfName, err) }()
	if strings.HasPrefix(req.Name, "._") {
		// Quietly ignore writes to special macOS files, without
		// triggering a notification.
		return nil, nil, libkbfs.WriteUnsupportedError{}.Errno()
	}
	return nil, nil, libkbfs.NewWriteUnsupportedError(libkbfs.BuildCanonicalPath(fl.PathType(), string(tlfName)))
}
<commit_msg>libfuse: handle singleTeam type in  `PathType`<commit_after>// Copyright 2016 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 libfuse

import (
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/kbfs/libkbfs"
	"github.com/keybase/kbfs/tlf"
	"golang.org/x/net/context"
)

// FolderList is a node that can list all of the logged-in user's
// favorite top-level folders, on either a public or private basis.
type FolderList struct {
	fs *FS
	// only accept public folders
	tlfType tlf.Type

	mu      sync.Mutex
	folders map[string]*TLF

	muRecentlyRemoved sync.RWMutex
	recentlyRemoved   map[libkbfs.CanonicalTlfName]bool
}

var _ fs.NodeAccesser = (*FolderList)(nil)

// Access implements fs.NodeAccesser interface for *FolderList.
func (*FolderList) Access(ctx context.Context, r *fuse.AccessRequest) error {
	if int(r.Uid) != os.Getuid() &&
		// Finder likes to use UID 0 for some operations. osxfuse already allows
		// ACCESS and GETXATTR requests from root to go through. This allows root
		// in ACCESS handler. See KBFS-1733 for more details.
		int(r.Uid) != 0 {
		// short path: not accessible by anybody other than root or the user who
		// executed the kbfsfuse process.
		return fuse.EPERM
	}

	if r.Mask&02 != 0 {
		return fuse.EPERM
	}

	return nil
}

var _ fs.Node = (*FolderList)(nil)

// Attr implements the fs.Node interface.
func (*FolderList) Attr(ctx context.Context, a *fuse.Attr) error {
	a.Mode = os.ModeDir | 0500
	a.Uid = uint32(os.Getuid())
	return nil
}

var _ fs.NodeRequestLookuper = (*FolderList)(nil)

func (fl *FolderList) reportErr(ctx context.Context,
	mode libkbfs.ErrorModeType, tlfName libkbfs.CanonicalTlfName, err error) {
	if err == nil {
		fl.fs.errLog.CDebugf(ctx, "Request complete")
		return
	}

	fl.fs.config.Reporter().ReportErr(ctx, tlfName, fl.tlfType, mode, err)
	// We just log the error as debug, rather than error, because it
	// might just indicate an expected error such as an ENOENT.
	//
	// TODO: Classify errors and escalate the logging level of the
	// important ones.
	fl.fs.errLog.CDebugf(ctx, err.Error())
}

func (fl *FolderList) addToRecentlyRemoved(name libkbfs.CanonicalTlfName) {
	func() {
		fl.muRecentlyRemoved.Lock()
		defer fl.muRecentlyRemoved.Unlock()
		if fl.recentlyRemoved == nil {
			fl.recentlyRemoved = make(map[libkbfs.CanonicalTlfName]bool)
		}
		fl.recentlyRemoved[name] = true
	}()
	fl.fs.execAfterDelay(time.Second, func() {
		fl.muRecentlyRemoved.Lock()
		defer fl.muRecentlyRemoved.Unlock()
		delete(fl.recentlyRemoved, name)
	})
}

func (fl *FolderList) isRecentlyRemoved(name libkbfs.CanonicalTlfName) bool {
	fl.muRecentlyRemoved.RLock()
	defer fl.muRecentlyRemoved.RUnlock()
	return fl.recentlyRemoved != nil && fl.recentlyRemoved[name]
}

func (fl *FolderList) addToFavorite(ctx context.Context, h *libkbfs.TlfHandle) (err error) {
	cName := h.GetCanonicalName()

	// `rmdir` command on macOS does a lookup after removing the dir. if the
	// TLF is recently removed, it's likely that this lookup is issued by the
	// `rmdir` command, and the lookup should not result in adding the dir to
	// favorites.
	if !fl.isRecentlyRemoved(cName) {
		fl.fs.log.CDebugf(ctx, "adding %s to favorites", cName)
		fl.fs.config.KBFSOps().AddFavorite(ctx, h.ToFavorite())
	} else {
		fl.fs.log.CDebugf(ctx, "recently removed; will skip adding %s to favorites and return ENOENT", cName)
		return fuse.ENOENT
	}
	return nil
}

// PathType returns PathType for this folder
func (fl *FolderList) PathType() libkbfs.PathType {
	switch fl.tlfType {
	case tlf.Private:
		return libkbfs.PrivatePathType
	case tlf.Public:
		return libkbfs.PublicPathType
	case tlf.SingleTeam:
		return libkbfs.SingleTeamPathType
	default:
		// TODO: support the team path.
		panic(fmt.Sprintf("Unsupported tlf type: %s", fl.tlfType))
	}
}

// Create implements the fs.NodeCreater interface for FolderList.
func (fl *FolderList) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (_ fs.Node, _ fs.Handle, err error) {
	fl.fs.log.CDebugf(ctx, "FL Create")
	tlfName := libkbfs.CanonicalTlfName(req.Name)
	defer func() { fl.reportErr(ctx, libkbfs.WriteMode, tlfName, err) }()
	if strings.HasPrefix(req.Name, "._") {
		// Quietly ignore writes to special macOS files, without
		// triggering a notification.
		return nil, nil, libkbfs.WriteUnsupportedError{}.Errno()
	}
	return nil, nil, libkbfs.NewWriteUnsupportedError(libkbfs.BuildCanonicalPath(fl.PathType(), string(tlfName)))
}

{'fraction_non_alphanumeric': 0.1168647116537665, 'fraction_numerical': 0.004142592390711872, 'mean_word_length': 4.091009988901221, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, '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': '5591886', 'n_tokens_mistral': 3459, 'n_tokens_neox': 3278, 'n_words': 899}