| // 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 |
| } |