blob: dafd11dc40378dac5bdcb57b9c44aefecc5d4f53 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package logreaderlib implements the LogFile interface from
// v.io/v23/services/logreader, which can be used to allow remote access to log
// files, and the Globbable interface from v.io/v23/services/mounttable to find
// the files in a logs directory.
package logreaderlib
import (
"io"
"math"
"os"
"path/filepath"
"strings"
"v.io/v23/context"
"v.io/v23/rpc"
"v.io/v23/services/logreader"
"v.io/v23/verror"
"v.io/x/lib/vlog"
)
const pkgPath = "v.io/x/ref/services/internal/logreaderlib"
var (
errOperationFailed = verror.Register(pkgPath+".errOperationFailed", verror.NoRetry, "{1:}{2:} operation failed{:_}")
)
// NewLogFileService returns a new log file server.
func NewLogFileService(root, suffix string) interface{} {
return logreader.LogFileServer(&logfileService{filepath.Clean(root), suffix})
}
// translateNameToFilename returns the file name that corresponds to the object
// name.
func translateNameToFilename(root, name string) (string, error) {
name = filepath.Join(strings.Split(name, "/")...)
p := filepath.Join(root, name)
// Make sure we're not asked to read a file outside of the root
// directory. This could happen if suffix contains "../", which get
// collapsed by filepath.Join().
if !strings.HasPrefix(p, root) {
return "", verror.New(errOperationFailed, nil, name)
}
return p, nil
}
// logfileService holds the state of a logfile invocation.
type logfileService struct {
// root is the root directory from which the object names are based.
root string
// suffix is the suffix of the current invocation that is assumed to
// be used as a relative object name to identify a log file.
suffix string
}
// Size returns the size of the log file, in bytes.
func (i *logfileService) Size(ctx *context.T, _ rpc.ServerCall) (int64, error) {
vlog.VI(1).Infof("%v.Size()", i.suffix)
fname, err := translateNameToFilename(i.root, i.suffix)
if err != nil {
return 0, err
}
fi, err := os.Stat(fname)
if err != nil {
if os.IsNotExist(err) {
return 0, verror.New(verror.ErrNoExist, ctx, fname)
}
vlog.Errorf("Stat(%v) failed: %v", fname, err)
return 0, verror.New(errOperationFailed, ctx, fname)
}
if fi.IsDir() {
return 0, verror.New(errOperationFailed, ctx, fname)
}
return fi.Size(), nil
}
// ReadLog returns log entries from the log file.
func (i *logfileService) ReadLog(ctx *context.T, call logreader.LogFileReadLogServerCall, startpos int64, numEntries int32, follow bool) (int64, error) {
vlog.VI(1).Infof("%v.ReadLog(%v, %v, %v)", i.suffix, startpos, numEntries, follow)
fname, err := translateNameToFilename(i.root, i.suffix)
if err != nil {
return 0, err
}
f, err := os.Open(fname)
if err != nil {
if os.IsNotExist(err) {
return 0, verror.New(verror.ErrNoExist, ctx, fname)
}
return 0, verror.New(errOperationFailed, ctx, fname)
}
reader := newFollowReader(ctx, f, startpos, follow)
if numEntries == logreader.AllEntries {
numEntries = int32(math.MaxInt32)
}
for n := int32(0); n < numEntries; n++ {
line, offset, err := reader.readLine()
if err == io.EOF && n > 0 {
return reader.tell(), nil
}
if err == io.EOF {
return reader.tell(), verror.NewErrEndOfFile(ctx)
}
if err != nil {
return reader.tell(), verror.New(errOperationFailed, ctx, fname)
}
if err := call.SendStream().Send(logreader.LogEntry{Position: offset, Line: line}); err != nil {
return reader.tell(), err
}
}
return reader.tell(), nil
}
// GlobChildren__ returns the list of files in a directory streamed on a
// channel. The list is empty if the object is a file.
func (i *logfileService) GlobChildren__(ctx *context.T, _ rpc.ServerCall) (<-chan string, error) {
vlog.VI(1).Infof("%v.GlobChildren__()", i.suffix)
dirName, err := translateNameToFilename(i.root, i.suffix)
if err != nil {
return nil, err
}
stat, err := os.Stat(dirName)
if err != nil {
if os.IsNotExist(err) {
return nil, verror.New(verror.ErrNoExist, ctx, dirName)
}
return nil, verror.New(errOperationFailed, ctx, dirName)
}
if !stat.IsDir() {
return nil, nil
}
ch := make(chan string)
go func() {
defer close(ch)
f, err := os.Open(dirName)
if err != nil {
return
}
defer f.Close()
for {
fi, err := f.Readdir(100)
if err != nil {
return
}
for _, file := range fi {
ch <- file.Name()
}
}
}()
return ch, nil
}