github.com/cs3org/reva/v2@v2.27.7/pkg/eosclient/eosgrpc/eosgrpc.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package eosgrpc
    20  
    21  import (
    22  	"bytes"
    23  	"context"
    24  	"encoding/hex"
    25  	"fmt"
    26  	"io"
    27  	"os"
    28  	"os/exec"
    29  	"path"
    30  	"strconv"
    31  	"strings"
    32  	"syscall"
    33  
    34  	"github.com/cs3org/reva/v2/pkg/appctx"
    35  
    36  	"github.com/cs3org/reva/v2/pkg/eosclient"
    37  	erpc "github.com/cs3org/reva/v2/pkg/eosclient/eosgrpc/eos_grpc"
    38  	"github.com/cs3org/reva/v2/pkg/errtypes"
    39  	"github.com/cs3org/reva/v2/pkg/logger"
    40  	"github.com/cs3org/reva/v2/pkg/storage/utils/acl"
    41  	"github.com/google/uuid"
    42  	"github.com/pkg/errors"
    43  	"github.com/rs/zerolog/log"
    44  	"google.golang.org/grpc"
    45  	"google.golang.org/grpc/credentials/insecure"
    46  )
    47  
    48  const (
    49  	versionPrefix = ".sys.v#."
    50  	// lwShareAttrKey = "reva.lwshare"
    51  )
    52  
    53  const (
    54  	// SystemAttr is the system extended attribute.
    55  	SystemAttr eosclient.AttrType = iota
    56  	// UserAttr is the user extended attribute.
    57  	UserAttr
    58  )
    59  
    60  // Options to configure the Client.
    61  type Options struct {
    62  
    63  	// UseKeyTabAuth changes will authenticate requests by using an EOS keytab.
    64  	UseKeytab bool
    65  
    66  	// Whether to maintain the same inode across various versions of a file.
    67  	// Requires extra metadata operations if set to true
    68  	VersionInvariant bool
    69  
    70  	// Set to true to use the local disk as a buffer for chunk
    71  	// reads from EOS. Default is false, i.e. pure streaming
    72  	ReadUsesLocalTemp bool
    73  
    74  	// Set to true to use the local disk as a buffer for chunk
    75  	// writes to EOS. Default is false, i.e. pure streaming
    76  	// Beware: in pure streaming mode the FST must support
    77  	// the HTTP chunked encoding
    78  	WriteUsesLocalTemp bool
    79  
    80  	// Location of the xrdcopy binary.
    81  	// Default is /opt/eos/xrootd/bin/xrdcopy.
    82  	XrdcopyBinary string
    83  
    84  	// URL of the EOS MGM.
    85  	// Default is root://eos-example.org
    86  	URL string
    87  
    88  	// URI of the EOS MGM grpc server
    89  	GrpcURI string
    90  
    91  	// Location on the local fs where to store reads.
    92  	// Defaults to os.TempDir()
    93  	CacheDirectory string
    94  
    95  	// Keytab is the location of the EOS keytab file.
    96  	Keytab string
    97  
    98  	// Authkey is the key that authorizes this client to connect to the GRPC service
    99  	// It's unclear whether this will be the final solution
   100  	Authkey string
   101  
   102  	// SecProtocol is the comma separated list of security protocols used by xrootd.
   103  	// For example: "sss, unix"
   104  	SecProtocol string
   105  }
   106  
   107  func (opt *Options) init() {
   108  
   109  	if opt.XrdcopyBinary == "" {
   110  		opt.XrdcopyBinary = "/opt/eos/xrootd/bin/xrdcopy"
   111  	}
   112  
   113  	if opt.URL == "" {
   114  		opt.URL = "root://eos-example.org"
   115  	}
   116  
   117  	if opt.CacheDirectory == "" {
   118  		opt.CacheDirectory = os.TempDir()
   119  	}
   120  
   121  }
   122  
   123  // Client performs actions against a EOS management node (MGM)
   124  // using the EOS GRPC interface.
   125  type Client struct {
   126  	opt    *Options
   127  	httpcl *EOSHTTPClient
   128  	cl     erpc.EosClient
   129  }
   130  
   131  // Create and connect a grpc eos Client
   132  func newgrpc(ctx context.Context, opt *Options) (erpc.EosClient, error) {
   133  	log := appctx.GetLogger(ctx)
   134  	log.Info().Str("Setting up GRPC towards ", "'"+opt.GrpcURI+"'").Msg("")
   135  
   136  	conn, err := grpc.NewClient(opt.GrpcURI, grpc.WithTransportCredentials(insecure.NewCredentials()))
   137  	if err != nil {
   138  		log.Warn().Str("Error connecting to ", "'"+opt.GrpcURI+"' ").Str("err", err.Error()).Msg("")
   139  	}
   140  
   141  	log.Debug().Str("Going to ping ", "'"+opt.GrpcURI+"' ").Msg("")
   142  	ecl := erpc.NewEosClient(conn)
   143  	// If we can't ping... just print warnings. In the case EOS is down, grpc will take care of
   144  	// connecting later
   145  	prq := new(erpc.PingRequest)
   146  	prq.Authkey = opt.Authkey
   147  	prq.Message = []byte("hi this is a ping from reva")
   148  	prep, err := ecl.Ping(ctx, prq)
   149  	if err != nil {
   150  		log.Warn().Str("Could not ping to ", "'"+opt.GrpcURI+"' ").Str("err", err.Error()).Msg("")
   151  	}
   152  
   153  	if prep == nil {
   154  		log.Warn().Str("Could not ping to ", "'"+opt.GrpcURI+"' ").Str("nil response", "").Msg("")
   155  	}
   156  
   157  	return ecl, nil
   158  }
   159  
   160  // New creates a new client with the given options.
   161  func New(opt *Options, httpOpts *HTTPOptions) (*Client, error) {
   162  	tlog := logger.New().With().Int("pid", os.Getpid()).Logger()
   163  	tlog.Debug().Str("Creating new eosgrpc client. opt: ", "'"+fmt.Sprintf("%#v", opt)+"' ").Msg("")
   164  
   165  	opt.init()
   166  	httpcl, err := NewEOSHTTPClient(httpOpts)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	tctx := appctx.WithLogger(context.Background(), &tlog)
   172  	cl, err := newgrpc(tctx, opt)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	return &Client{
   178  		opt:    opt,
   179  		httpcl: httpcl,
   180  		cl:     cl,
   181  	}, nil
   182  }
   183  
   184  // If the error is not nil, take that
   185  // If there is an error coming from EOS, erturn a descriptive error
   186  func (c *Client) getRespError(rsp *erpc.NSResponse, err error) error {
   187  	if err != nil {
   188  		return err
   189  	}
   190  	if rsp == nil || rsp.Error == nil || rsp.Error.Code == 0 {
   191  		return nil
   192  	}
   193  
   194  	return errtypes.InternalError("Err from EOS: " + fmt.Sprintf("%#v", rsp.Error))
   195  }
   196  
   197  // Common code to create and initialize a NSRequest
   198  func (c *Client) initNSRequest(ctx context.Context, auth eosclient.Authorization) (*erpc.NSRequest, error) {
   199  	// Stuff filename, uid, gid into the MDRequest type
   200  
   201  	log := appctx.GetLogger(ctx)
   202  	log.Debug().Str("(uid,gid)", "("+auth.Role.UID+","+auth.Role.GID+")").Msg("New grpcNS req")
   203  
   204  	rq := new(erpc.NSRequest)
   205  	rq.Role = new(erpc.RoleId)
   206  
   207  	uidInt, err := strconv.ParseUint(auth.Role.UID, 10, 64)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	gidInt, err := strconv.ParseUint(auth.Role.GID, 10, 64)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	rq.Role.Uid = uidInt
   216  	rq.Role.Gid = gidInt
   217  	rq.Authkey = c.opt.Authkey
   218  
   219  	return rq, nil
   220  }
   221  
   222  // Common code to create and initialize a NSRequest
   223  func (c *Client) initMDRequest(ctx context.Context, auth eosclient.Authorization) (*erpc.MDRequest, error) {
   224  	// Stuff filename, uid, gid into the MDRequest type
   225  
   226  	log := appctx.GetLogger(ctx)
   227  	log.Debug().Str("(uid,gid)", "("+auth.Role.UID+","+auth.Role.GID+")").Msg("New grpcMD req")
   228  
   229  	mdrq := new(erpc.MDRequest)
   230  	mdrq.Role = new(erpc.RoleId)
   231  
   232  	uidInt, err := strconv.ParseUint(auth.Role.UID, 10, 64)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	gidInt, err := strconv.ParseUint(auth.Role.GID, 10, 64)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  	mdrq.Role.Uid = uidInt
   241  	mdrq.Role.Gid = gidInt
   242  
   243  	mdrq.Authkey = c.opt.Authkey
   244  
   245  	return mdrq, nil
   246  }
   247  
   248  // AddACL adds an new acl to EOS with the given aclType.
   249  func (c *Client) AddACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, pos uint, a *acl.Entry) error {
   250  
   251  	log := appctx.GetLogger(ctx)
   252  	log.Info().Str("func", "AddACL").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
   253  
   254  	// Init a new NSRequest
   255  	rq, err := c.initNSRequest(ctx, rootAuth)
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	// workaround to be root
   261  	// TODO: removed once fixed in eos grpc
   262  	rq.Role.Gid = 1
   263  
   264  	msg := new(erpc.NSRequest_AclRequest)
   265  	msg.Cmd = erpc.NSRequest_AclRequest_ACL_COMMAND(erpc.NSRequest_AclRequest_ACL_COMMAND_value["MODIFY"])
   266  	msg.Type = erpc.NSRequest_AclRequest_ACL_TYPE(erpc.NSRequest_AclRequest_ACL_TYPE_value["SYS_ACL"])
   267  	msg.Recursive = true
   268  	msg.Rule = a.CitrineSerialize()
   269  
   270  	msg.Id = new(erpc.MDId)
   271  	msg.Id.Path = []byte(path)
   272  
   273  	rq.Command = &erpc.NSRequest_Acl{Acl: msg}
   274  
   275  	// Now send the req and see what happens
   276  	resp, err := c.cl.Exec(context.Background(), rq)
   277  	e := c.getRespError(resp, err)
   278  	if e != nil {
   279  		log.Error().Str("func", "AddACL").Str("path", path).Str("err", e.Error()).Msg("")
   280  		return e
   281  	}
   282  
   283  	if resp == nil {
   284  		return errtypes.NotFound(fmt.Sprintf("Path: %s", path))
   285  	}
   286  
   287  	log.Debug().Str("func", "AddACL").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
   288  
   289  	return err
   290  
   291  }
   292  
   293  // RemoveACL removes the acl from EOS.
   294  func (c *Client) RemoveACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, a *acl.Entry) error {
   295  
   296  	log := appctx.GetLogger(ctx)
   297  	log.Info().Str("func", "RemoveACL").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
   298  
   299  	acls, err := c.getACLForPath(ctx, auth, path)
   300  	if err != nil {
   301  		return err
   302  	}
   303  
   304  	acls.DeleteEntry(a.Type, a.Qualifier)
   305  	sysACL := acls.Serialize()
   306  
   307  	// Init a new NSRequest
   308  	rq, err := c.initNSRequest(ctx, auth)
   309  	if err != nil {
   310  		return err
   311  	}
   312  
   313  	msg := new(erpc.NSRequest_AclRequest)
   314  	msg.Cmd = erpc.NSRequest_AclRequest_ACL_COMMAND(erpc.NSRequest_AclRequest_ACL_COMMAND_value["MODIFY"])
   315  	msg.Type = erpc.NSRequest_AclRequest_ACL_TYPE(erpc.NSRequest_AclRequest_ACL_TYPE_value["SYS_ACL"])
   316  	msg.Recursive = true
   317  	msg.Rule = sysACL
   318  
   319  	msg.Id = new(erpc.MDId)
   320  	msg.Id.Path = []byte(path)
   321  
   322  	rq.Command = &erpc.NSRequest_Acl{Acl: msg}
   323  
   324  	// Now send the req and see what happens
   325  	resp, err := c.cl.Exec(context.Background(), rq)
   326  	e := c.getRespError(resp, err)
   327  	if e != nil {
   328  		log.Error().Str("func", "RemoveACL").Str("path", path).Str("err", e.Error()).Msg("")
   329  		return e
   330  	}
   331  
   332  	if resp == nil {
   333  		return errtypes.NotFound(fmt.Sprintf("Path: %s", path))
   334  	}
   335  
   336  	log.Debug().Str("func", "RemoveACL").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
   337  
   338  	return err
   339  
   340  }
   341  
   342  // UpdateACL updates the EOS acl.
   343  func (c *Client) UpdateACL(ctx context.Context, auth, rootAuth eosclient.Authorization, path string, position uint, a *acl.Entry) error {
   344  	return c.AddACL(ctx, auth, rootAuth, path, position, a)
   345  }
   346  
   347  // GetACL for a file
   348  func (c *Client) GetACL(ctx context.Context, auth eosclient.Authorization, path, aclType, target string) (*acl.Entry, error) {
   349  
   350  	log := appctx.GetLogger(ctx)
   351  	log.Info().Str("func", "GetACL").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
   352  
   353  	acls, err := c.ListACLs(ctx, auth, path)
   354  	if err != nil {
   355  		return nil, err
   356  	}
   357  	for _, a := range acls {
   358  		if a.Type == aclType && a.Qualifier == target {
   359  			return a, nil
   360  		}
   361  	}
   362  	return nil, errtypes.NotFound(fmt.Sprintf("%s:%s", aclType, target))
   363  
   364  }
   365  
   366  // ListACLs returns the list of ACLs present under the given path.
   367  // EOS returns uids/gid for Citrine version and usernames for older versions.
   368  // For Citire we need to convert back the uid back to username.
   369  func (c *Client) ListACLs(ctx context.Context, auth eosclient.Authorization, path string) ([]*acl.Entry, error) {
   370  	log := appctx.GetLogger(ctx)
   371  	log.Info().Str("func", "ListACLs").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
   372  
   373  	parsedACLs, err := c.getACLForPath(ctx, auth, path)
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  
   378  	// EOS Citrine ACLs are stored with uid. The UID will be resolved to the
   379  	// user opaque ID at the eosfs level.
   380  	return parsedACLs.Entries, nil
   381  }
   382  
   383  func (c *Client) getACLForPath(ctx context.Context, auth eosclient.Authorization, path string) (*acl.ACLs, error) {
   384  	log := appctx.GetLogger(ctx)
   385  	log.Info().Str("func", "GetACLForPath").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
   386  
   387  	// Initialize the common fields of the NSReq
   388  	rq, err := c.initNSRequest(ctx, auth)
   389  	if err != nil {
   390  		return nil, err
   391  	}
   392  
   393  	msg := new(erpc.NSRequest_AclRequest)
   394  	msg.Cmd = erpc.NSRequest_AclRequest_ACL_COMMAND(erpc.NSRequest_AclRequest_ACL_COMMAND_value["LIST"])
   395  	msg.Type = erpc.NSRequest_AclRequest_ACL_TYPE(erpc.NSRequest_AclRequest_ACL_TYPE_value["SYS_ACL"])
   396  	msg.Recursive = true
   397  
   398  	msg.Id = new(erpc.MDId)
   399  	msg.Id.Path = []byte(path)
   400  
   401  	rq.Command = &erpc.NSRequest_Acl{Acl: msg}
   402  
   403  	// Now send the req and see what happens
   404  	resp, err := c.cl.Exec(context.Background(), rq)
   405  	e := c.getRespError(resp, err)
   406  	if e != nil {
   407  		log.Error().Str("func", "GetACLForPath").Str("path", path).Str("err", e.Error()).Msg("")
   408  		return nil, e
   409  	}
   410  
   411  	if resp == nil {
   412  		return nil, errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", auth.Role.UID, path))
   413  	}
   414  
   415  	log.Debug().Str("func", "GetACLForPath").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
   416  
   417  	if resp.Acl == nil {
   418  		return nil, errtypes.InternalError(fmt.Sprintf("nil acl for uid: '%s' path: '%s'", auth.Role.UID, path))
   419  	}
   420  
   421  	if resp.GetError() != nil {
   422  		log.Error().Str("func", "GetACLForPath").Str("uid", auth.Role.UID).Str("path", path).Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp")
   423  	}
   424  
   425  	aclret, err := acl.Parse(resp.Acl.Rule, acl.ShortTextForm)
   426  
   427  	// Now loop and build the correct return value
   428  
   429  	return aclret, err
   430  }
   431  
   432  // GetFileInfoByInode returns the FileInfo by the given inode
   433  func (c *Client) GetFileInfoByInode(ctx context.Context, auth eosclient.Authorization, inode uint64) (*eosclient.FileInfo, error) {
   434  	log := appctx.GetLogger(ctx)
   435  	log.Info().Str("func", "GetFileInfoByInode").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Uint64("inode", inode).Msg("")
   436  
   437  	// Initialize the common fields of the MDReq
   438  	mdrq, err := c.initMDRequest(ctx, auth)
   439  	if err != nil {
   440  		return nil, err
   441  	}
   442  
   443  	// Stuff filename, uid, gid into the MDRequest type
   444  	mdrq.Type = erpc.TYPE_STAT
   445  	mdrq.Id = new(erpc.MDId)
   446  	mdrq.Id.Ino = inode
   447  
   448  	// Now send the req and see what happens
   449  	resp, err := c.cl.MD(context.Background(), mdrq)
   450  	if err != nil {
   451  		log.Error().Err(err).Uint64("inode", inode).Str("err", err.Error())
   452  
   453  		return nil, err
   454  	}
   455  	rsp, err := resp.Recv()
   456  	if err != nil {
   457  		log.Error().Err(err).Uint64("inode", inode).Str("err", err.Error())
   458  		return nil, err
   459  	}
   460  
   461  	if rsp == nil {
   462  		return nil, errtypes.InternalError(fmt.Sprintf("nil response for inode: '%d'", inode))
   463  	}
   464  
   465  	log.Debug().Uint64("inode", inode).Str("rsp:", fmt.Sprintf("%#v", rsp)).Msg("grpc response")
   466  
   467  	info, err := c.grpcMDResponseToFileInfo(rsp)
   468  	if err != nil {
   469  		return nil, err
   470  	}
   471  
   472  	if c.opt.VersionInvariant && isVersionFolder(info.File) {
   473  		info, err = c.getFileInfoFromVersion(ctx, auth, info.File)
   474  		if err != nil {
   475  			return nil, err
   476  		}
   477  		info.Inode = inode
   478  	}
   479  
   480  	log.Debug().Str("func", "GetFileInfoByInode").Uint64("inode", inode).Msg("")
   481  	return c.fixupACLs(ctx, auth, info), nil
   482  }
   483  
   484  func (c *Client) fixupACLs(ctx context.Context, auth eosclient.Authorization, info *eosclient.FileInfo) *eosclient.FileInfo {
   485  
   486  	// Append the ACLs that are described by the xattr sys.acl entry
   487  	a, err := acl.Parse(info.Attrs["sys.acl"], acl.ShortTextForm)
   488  	if err == nil {
   489  		if info.SysACL != nil {
   490  			info.SysACL.Entries = append(info.SysACL.Entries, a.Entries...)
   491  		} else {
   492  			info.SysACL = a
   493  		}
   494  	}
   495  
   496  	// We need to inherit the ACLs for the parent directory as these are not available for files
   497  	if !info.IsDir {
   498  		parentInfo, err := c.GetFileInfoByPath(ctx, auth, path.Dir(info.File))
   499  		// Even if this call fails, at least return the current file object
   500  		if err == nil {
   501  			info.SysACL.Entries = append(info.SysACL.Entries, parentInfo.SysACL.Entries...)
   502  		}
   503  	}
   504  	return info
   505  }
   506  
   507  // SetAttr sets an extended attributes on a path.
   508  func (c *Client) SetAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, errorIfExists, recursive bool, path string) error {
   509  	log := appctx.GetLogger(ctx)
   510  	log.Info().Str("func", "SetAttr").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
   511  
   512  	// Initialize the common fields of the NSReq
   513  	rq, err := c.initNSRequest(ctx, auth)
   514  	if err != nil {
   515  		return err
   516  	}
   517  
   518  	msg := new(erpc.NSRequest_SetXAttrRequest)
   519  
   520  	var m = map[string][]byte{attr.GetKey(): []byte(attr.Val)}
   521  	msg.Xattrs = m
   522  	msg.Recursive = recursive
   523  
   524  	msg.Id = new(erpc.MDId)
   525  	msg.Id.Path = []byte(path)
   526  
   527  	if errorIfExists {
   528  		msg.Create = true
   529  	}
   530  
   531  	rq.Command = &erpc.NSRequest_Xattr{Xattr: msg}
   532  
   533  	// Now send the req and see what happens
   534  	resp, err := c.cl.Exec(ctx, rq)
   535  	e := c.getRespError(resp, err)
   536  
   537  	if resp != nil && resp.Error != nil && resp.Error.Code == 17 {
   538  		return eosclient.AttrAlreadyExistsError
   539  	}
   540  
   541  	if e != nil {
   542  		log.Error().Str("func", "SetAttr").Str("path", path).Str("err", e.Error()).Msg("")
   543  		return e
   544  	}
   545  
   546  	if resp == nil {
   547  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' gid: '%s' path: '%s'", auth.Role.UID, auth.Role.GID, path))
   548  	}
   549  
   550  	if resp.GetError() != nil {
   551  		log.Error().Str("func", "setAttr").Str("path", path).Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative result")
   552  	}
   553  
   554  	return err
   555  
   556  }
   557  
   558  // UnsetAttr unsets an extended attribute on a path.
   559  func (c *Client) UnsetAttr(ctx context.Context, auth eosclient.Authorization, attr *eosclient.Attribute, recursive bool, path string) error {
   560  	log := appctx.GetLogger(ctx)
   561  	log.Info().Str("func", "UnsetAttr").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
   562  
   563  	// Initialize the common fields of the NSReq
   564  	rq, err := c.initNSRequest(ctx, auth)
   565  	if err != nil {
   566  		return err
   567  	}
   568  
   569  	msg := new(erpc.NSRequest_SetXAttrRequest)
   570  
   571  	var ktd = []string{attr.GetKey()}
   572  	msg.Keystodelete = ktd
   573  	msg.Recursive = recursive
   574  
   575  	msg.Id = new(erpc.MDId)
   576  	msg.Id.Path = []byte(path)
   577  
   578  	rq.Command = &erpc.NSRequest_Xattr{Xattr: msg}
   579  
   580  	// Now send the req and see what happens
   581  	resp, err := c.cl.Exec(ctx, rq)
   582  
   583  	if resp != nil && resp.Error != nil && resp.Error.Code == 61 {
   584  		return eosclient.AttrNotExistsError
   585  	}
   586  
   587  	e := c.getRespError(resp, err)
   588  	if e != nil {
   589  		log.Error().Str("func", "UnsetAttr").Str("path", path).Str("err", e.Error()).Msg("")
   590  		return e
   591  	}
   592  
   593  	if resp == nil {
   594  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' gid: '%s' path: '%s'", auth.Role.UID, auth.Role.GID, path))
   595  	}
   596  
   597  	if resp.GetError() != nil {
   598  		log.Error().Str("func", "UnsetAttr").Str("path", path).Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp")
   599  	}
   600  	return err
   601  
   602  }
   603  
   604  // GetAttr returns the attribute specified by key
   605  func (c *Client) GetAttr(ctx context.Context, auth eosclient.Authorization, key, path string) (*eosclient.Attribute, error) {
   606  	info, err := c.GetFileInfoByPath(ctx, auth, path)
   607  	if err != nil {
   608  		return nil, err
   609  	}
   610  
   611  	for k, v := range info.Attrs {
   612  		if k == key {
   613  			attr, err := getAttribute(k, v)
   614  			if err != nil {
   615  				return nil, errors.Wrap(err, fmt.Sprintf("eosgrpc: cannot parse attribute key=%s value=%s", k, v))
   616  			}
   617  			return attr, nil
   618  		}
   619  	}
   620  	return nil, errtypes.NotFound(fmt.Sprintf("key %s not found", key))
   621  }
   622  
   623  func getAttribute(key, val string) (*eosclient.Attribute, error) {
   624  	// key is in the form sys.forced.checksum
   625  	type2key := strings.SplitN(key, ".", 2) // type2key = ["sys", "forced.checksum"]
   626  	if len(type2key) != 2 {
   627  		return nil, errtypes.InternalError("wrong attr format to deserialize")
   628  	}
   629  	t, err := eosclient.AttrStringToType(type2key[0])
   630  	if err != nil {
   631  		return nil, err
   632  	}
   633  	attr := &eosclient.Attribute{
   634  		Type: t,
   635  		Key:  type2key[1],
   636  		Val:  val,
   637  	}
   638  	return attr, nil
   639  }
   640  
   641  // GetFileInfoByPath returns the FilInfo at the given path
   642  func (c *Client) GetFileInfoByPath(ctx context.Context, auth eosclient.Authorization, path string) (*eosclient.FileInfo, error) {
   643  	log := appctx.GetLogger(ctx)
   644  	log.Info().Str("func", "GetFileInfoByPath").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
   645  
   646  	// Initialize the common fields of the MDReq
   647  	mdrq, err := c.initMDRequest(ctx, auth)
   648  	if err != nil {
   649  		return nil, err
   650  	}
   651  
   652  	mdrq.Type = erpc.TYPE_STAT
   653  	mdrq.Id = new(erpc.MDId)
   654  	mdrq.Id.Path = []byte(path)
   655  
   656  	// Now send the req and see what happens
   657  	resp, err := c.cl.MD(ctx, mdrq)
   658  	if err != nil {
   659  		log.Error().Str("func", "GetFileInfoByPath").Err(err).Str("path", path).Str("err", err.Error()).Msg("")
   660  
   661  		return nil, err
   662  	}
   663  	rsp, err := resp.Recv()
   664  	if err != nil {
   665  		log.Error().Str("func", "GetFileInfoByPath").Err(err).Str("path", path).Str("err", err.Error()).Msg("")
   666  
   667  		// FIXME: this is very very bad and poisonous for the project!!!!!!!
   668  		// Apparently here we have to assume that an error in Recv() means "file not found"
   669  		// - "File not found is not an error", it's a legitimate result of a legitimate check
   670  		// - Assuming that any error means file not found is doubly poisonous
   671  		return nil, errtypes.NotFound(err.Error())
   672  		// return nil, nil
   673  	}
   674  
   675  	if rsp == nil {
   676  		return nil, errtypes.NotFound(fmt.Sprintf("%s:%s", "acltype", path))
   677  	}
   678  
   679  	log.Debug().Str("func", "GetFileInfoByPath").Str("path", path).Str("rsp:", fmt.Sprintf("%#v", rsp)).Msg("grpc response")
   680  
   681  	info, err := c.grpcMDResponseToFileInfo(rsp)
   682  	if err != nil {
   683  		return nil, err
   684  	}
   685  
   686  	if c.opt.VersionInvariant && !isVersionFolder(path) && !info.IsDir {
   687  		inode, err := c.getVersionFolderInode(ctx, auth, path)
   688  		if err != nil {
   689  			return nil, err
   690  		}
   691  		info.Inode = inode
   692  	}
   693  
   694  	return c.fixupACLs(ctx, auth, info), nil
   695  }
   696  
   697  // GetFileInfoByFXID returns the FileInfo by the given file id in hexadecimal
   698  func (c *Client) GetFileInfoByFXID(ctx context.Context, auth eosclient.Authorization, fxid string) (*eosclient.FileInfo, error) {
   699  	return nil, errtypes.NotSupported("eosgrpc: GetFileInfoByFXID not implemented")
   700  }
   701  
   702  // GetQuota gets the quota of a user on the quota node defined by path
   703  func (c *Client) GetQuota(ctx context.Context, username string, rootAuth eosclient.Authorization, path string) (*eosclient.QuotaInfo, error) {
   704  	log := appctx.GetLogger(ctx)
   705  	log.Info().Str("func", "GetQuota").Str("rootuid,rootgid", rootAuth.Role.UID+","+rootAuth.Role.GID).Str("username", username).Str("path", path).Msg("")
   706  
   707  	// Initialize the common fields of the NSReq
   708  	rq, err := c.initNSRequest(ctx, rootAuth)
   709  	if err != nil {
   710  		return nil, err
   711  	}
   712  
   713  	msg := new(erpc.NSRequest_QuotaRequest)
   714  	msg.Path = []byte(path)
   715  	msg.Id = new(erpc.RoleId)
   716  	msg.Op = erpc.QUOTAOP_GET
   717  	// Eos filters the returned quotas by username. This means that EOS must know it, someone
   718  	// must have created an user with that name
   719  	msg.Id.Username = username
   720  	rq.Command = &erpc.NSRequest_Quota{Quota: msg}
   721  
   722  	// Now send the req and see what happens
   723  	resp, err := c.cl.Exec(ctx, rq)
   724  	e := c.getRespError(resp, err)
   725  	if e != nil {
   726  		return nil, e
   727  	}
   728  
   729  	if resp == nil {
   730  		return nil, errtypes.InternalError(fmt.Sprintf("nil response for username: '%s' path: '%s'", username, path))
   731  	}
   732  
   733  	if resp.GetError() != nil {
   734  		log.Error().Str("func", "GetQuota").Str("username", username).Str("info:", fmt.Sprintf("%#v", resp)).Int64("eoserrcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp")
   735  	} else {
   736  		log.Debug().Str("func", "GetQuota").Str("username", username).Str("info:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
   737  	}
   738  
   739  	if resp.Quota == nil {
   740  		return nil, errtypes.InternalError(fmt.Sprintf("nil quota response? path: '%s'", path))
   741  	}
   742  
   743  	if resp.Quota.Code != 0 {
   744  		return nil, errtypes.InternalError(fmt.Sprintf("Quota error from eos. info: '%#v'", resp.Quota))
   745  	}
   746  
   747  	qi := new(eosclient.QuotaInfo)
   748  	if resp == nil {
   749  		return nil, errtypes.InternalError("Out of memory")
   750  	}
   751  
   752  	// Let's loop on all the quotas that match this uid (apparently there can be many)
   753  	// If there are many for this node, we sum them up
   754  	for i := 0; i < len(resp.Quota.Quotanode); i++ {
   755  		log.Debug().Str("func", "GetQuota").Str("quotanode:", fmt.Sprintf("%d: %#v", i, resp.Quota.Quotanode[i])).Msg("")
   756  
   757  		mx := int64(resp.Quota.Quotanode[i].Maxlogicalbytes) - int64(resp.Quota.Quotanode[i].Usedbytes)
   758  		if mx < 0 {
   759  			mx = 0
   760  		}
   761  		qi.AvailableBytes += uint64(mx)
   762  		qi.UsedBytes += resp.Quota.Quotanode[i].Usedbytes
   763  
   764  		mx = int64(resp.Quota.Quotanode[i].Maxfiles) - int64(resp.Quota.Quotanode[i].Usedfiles)
   765  		if mx < 0 {
   766  			mx = 0
   767  		}
   768  		qi.AvailableInodes += uint64(mx)
   769  		qi.UsedInodes += resp.Quota.Quotanode[i].Usedfiles
   770  	}
   771  
   772  	return qi, err
   773  
   774  }
   775  
   776  // SetQuota sets the quota of a user on the quota node defined by path
   777  func (c *Client) SetQuota(ctx context.Context, rootAuth eosclient.Authorization, info *eosclient.SetQuotaInfo) error {
   778  	log := appctx.GetLogger(ctx)
   779  	log.Info().Str("func", "SetQuota").Str("info:", fmt.Sprintf("%#v", info)).Msg("")
   780  
   781  	// EOS does not have yet this command... work in progress, this is a draft piece of code
   782  	// return errtypes.NotSupported("eosgrpc: SetQuota not implemented")
   783  
   784  	// Initialize the common fields of the NSReq
   785  	rq, err := c.initNSRequest(ctx, rootAuth)
   786  	if err != nil {
   787  		return err
   788  
   789  	}
   790  
   791  	msg := new(erpc.NSRequest_QuotaRequest)
   792  	msg.Path = []byte(info.QuotaNode)
   793  	msg.Id = new(erpc.RoleId)
   794  	uidInt, err := strconv.ParseUint(info.UID, 10, 64)
   795  	if err != nil {
   796  		return err
   797  	}
   798  
   799  	// We set a quota for an user, not a group!
   800  	msg.Id.Uid = uidInt
   801  	msg.Id.Gid = 0
   802  	msg.Id.Username = info.Username
   803  	msg.Op = erpc.QUOTAOP_SET
   804  	msg.Maxbytes = info.MaxBytes
   805  	msg.Maxfiles = info.MaxFiles
   806  	rq.Command = &erpc.NSRequest_Quota{Quota: msg}
   807  
   808  	// Now send the req and see what happens
   809  	resp, err := c.cl.Exec(ctx, rq)
   810  	e := c.getRespError(resp, err)
   811  	if e != nil {
   812  		return e
   813  	}
   814  
   815  	if resp == nil {
   816  		return errtypes.InternalError(fmt.Sprintf("nil response for info: '%#v'", info))
   817  	}
   818  
   819  	if resp.GetError() != nil {
   820  		log.Error().Str("func", "SetQuota").Str("info:", fmt.Sprintf("%#v", resp)).Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp")
   821  	} else {
   822  		log.Debug().Str("func", "SetQuota").Str("info:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
   823  	}
   824  
   825  	if resp.Quota == nil {
   826  		return errtypes.InternalError(fmt.Sprintf("nil quota response? info: '%#v'", info))
   827  	}
   828  
   829  	if resp.Quota.Code != 0 {
   830  		return errtypes.InternalError(fmt.Sprintf("Quota error from eos. quota: '%#v'", resp.Quota))
   831  	}
   832  
   833  	log.Debug().Str("func", "GetQuota").Str("quotanodes", fmt.Sprintf("%d", len(resp.Quota.Quotanode))).Msg("grpc response")
   834  
   835  	return err
   836  }
   837  
   838  // Touch creates a 0-size,0-replica file in the EOS namespace.
   839  func (c *Client) Touch(ctx context.Context, auth eosclient.Authorization, path string) error {
   840  	log := appctx.GetLogger(ctx)
   841  	log.Info().Str("func", "Touch").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
   842  
   843  	// Initialize the common fields of the NSReq
   844  	rq, err := c.initNSRequest(ctx, auth)
   845  	if err != nil {
   846  		return err
   847  	}
   848  
   849  	msg := new(erpc.NSRequest_TouchRequest)
   850  
   851  	msg.Id = new(erpc.MDId)
   852  	msg.Id.Path = []byte(path)
   853  
   854  	rq.Command = &erpc.NSRequest_Touch{Touch: msg}
   855  
   856  	// Now send the req and see what happens
   857  	resp, err := c.cl.Exec(ctx, rq)
   858  	e := c.getRespError(resp, err)
   859  	if e != nil {
   860  		log.Error().Str("func", "Touch").Str("path", path).Str("err", e.Error()).Msg("")
   861  		return e
   862  	}
   863  
   864  	if resp == nil {
   865  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", auth.Role.UID, path))
   866  	}
   867  
   868  	log.Debug().Str("func", "Touch").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
   869  
   870  	return err
   871  
   872  }
   873  
   874  // Chown given path
   875  func (c *Client) Chown(ctx context.Context, auth, chownAuth eosclient.Authorization, path string) error {
   876  	log := appctx.GetLogger(ctx)
   877  	log.Info().Str("func", "Chown").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("chownuid,chowngid", chownAuth.Role.UID+","+chownAuth.Role.GID).Str("path", path).Msg("")
   878  
   879  	// Initialize the common fields of the NSReq
   880  	rq, err := c.initNSRequest(ctx, auth)
   881  	if err != nil {
   882  		return err
   883  	}
   884  
   885  	msg := new(erpc.NSRequest_ChownRequest)
   886  	msg.Owner = new(erpc.RoleId)
   887  	msg.Owner.Uid, err = strconv.ParseUint(chownAuth.Role.UID, 10, 64)
   888  	if err != nil {
   889  		return err
   890  	}
   891  	msg.Owner.Gid, err = strconv.ParseUint(chownAuth.Role.GID, 10, 64)
   892  	if err != nil {
   893  		return err
   894  	}
   895  
   896  	msg.Id = new(erpc.MDId)
   897  	msg.Id.Path = []byte(path)
   898  
   899  	rq.Command = &erpc.NSRequest_Chown{Chown: msg}
   900  
   901  	// Now send the req and see what happens
   902  	resp, err := c.cl.Exec(ctx, rq)
   903  	e := c.getRespError(resp, err)
   904  	if e != nil {
   905  		log.Error().Str("func", "Chown").Str("path", path).Str("err", e.Error()).Msg("")
   906  		return e
   907  	}
   908  
   909  	if resp == nil {
   910  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' chownuid: '%s' path: '%s'", auth.Role.UID, chownAuth.Role.UID, path))
   911  	}
   912  
   913  	log.Debug().Str("func", "Chown").Str("path", path).Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("chownuid,chowngid", chownAuth.Role.UID+","+chownAuth.Role.GID).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
   914  
   915  	return err
   916  
   917  }
   918  
   919  // Chmod given path
   920  func (c *Client) Chmod(ctx context.Context, auth eosclient.Authorization, mode, path string) error {
   921  	log := appctx.GetLogger(ctx)
   922  	log.Info().Str("func", "Chmod").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("mode", mode).Str("path", path).Msg("")
   923  
   924  	// Initialize the common fields of the NSReq
   925  	rq, err := c.initNSRequest(ctx, auth)
   926  	if err != nil {
   927  		return err
   928  	}
   929  
   930  	msg := new(erpc.NSRequest_ChmodRequest)
   931  
   932  	md, err := strconv.ParseUint(mode, 8, 64)
   933  	if err != nil {
   934  		return err
   935  	}
   936  	msg.Mode = int64(md)
   937  
   938  	msg.Id = new(erpc.MDId)
   939  	msg.Id.Path = []byte(path)
   940  
   941  	rq.Command = &erpc.NSRequest_Chmod{Chmod: msg}
   942  
   943  	// Now send the req and see what happens
   944  	resp, err := c.cl.Exec(ctx, rq)
   945  	e := c.getRespError(resp, err)
   946  	if e != nil {
   947  		log.Error().Str("func", "Chmod").Str("path ", path).Str("err", e.Error()).Msg("")
   948  		return e
   949  	}
   950  
   951  	if resp == nil {
   952  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' mode: '%s' path: '%s'", auth.Role.UID, mode, path))
   953  	}
   954  
   955  	log.Debug().Str("func", "Chmod").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
   956  
   957  	return err
   958  
   959  }
   960  
   961  // CreateDir creates a directory at the given path
   962  func (c *Client) CreateDir(ctx context.Context, auth eosclient.Authorization, path string) error {
   963  	log := appctx.GetLogger(ctx)
   964  	log.Info().Str("func", "Createdir").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
   965  
   966  	// Initialize the common fields of the NSReq
   967  	rq, err := c.initNSRequest(ctx, auth)
   968  	if err != nil {
   969  		return err
   970  	}
   971  
   972  	msg := new(erpc.NSRequest_MkdirRequest)
   973  
   974  	// Let's put 750 as permissions, assuming that EOS will apply some mask
   975  	md, err := strconv.ParseUint("750", 8, 64)
   976  	if err != nil {
   977  		return err
   978  	}
   979  	msg.Mode = int64(md)
   980  	msg.Recursive = true
   981  	msg.Id = new(erpc.MDId)
   982  	msg.Id.Path = []byte(path)
   983  
   984  	rq.Command = &erpc.NSRequest_Mkdir{Mkdir: msg}
   985  
   986  	// Now send the req and see what happens
   987  	resp, err := c.cl.Exec(ctx, rq)
   988  	e := c.getRespError(resp, err)
   989  	if e != nil {
   990  		log.Error().Str("func", "Createdir").Str("path", path).Str("err", e.Error()).Msg("")
   991  		return e
   992  	}
   993  
   994  	if resp == nil {
   995  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", auth.Role.UID, path))
   996  	}
   997  
   998  	log.Debug().Str("func", "Createdir").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
   999  
  1000  	return err
  1001  
  1002  }
  1003  
  1004  func (c *Client) rm(ctx context.Context, auth eosclient.Authorization, path string, noRecycle bool) error {
  1005  	log := appctx.GetLogger(ctx)
  1006  	log.Info().Str("func", "rm").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
  1007  
  1008  	// Initialize the common fields of the NSReq
  1009  	rq, err := c.initNSRequest(ctx, auth)
  1010  	if err != nil {
  1011  		return err
  1012  	}
  1013  
  1014  	msg := new(erpc.NSRequest_UnlinkRequest)
  1015  
  1016  	msg.Id = new(erpc.MDId)
  1017  	msg.Id.Path = []byte(path)
  1018  	msg.Norecycle = noRecycle
  1019  
  1020  	rq.Command = &erpc.NSRequest_Unlink{Unlink: msg}
  1021  
  1022  	// Now send the req and see what happens
  1023  	resp, err := c.cl.Exec(ctx, rq)
  1024  	e := c.getRespError(resp, err)
  1025  	if e != nil {
  1026  		log.Error().Str("func", "rm").Str("path", path).Str("err", e.Error()).Msg("")
  1027  		return e
  1028  	}
  1029  
  1030  	if resp == nil {
  1031  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", auth.Role.UID, path))
  1032  	}
  1033  
  1034  	log.Debug().Str("func", "rm").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
  1035  
  1036  	return err
  1037  
  1038  }
  1039  
  1040  func (c *Client) rmdir(ctx context.Context, auth eosclient.Authorization, path string, noRecycle bool) error {
  1041  	log := appctx.GetLogger(ctx)
  1042  	log.Info().Str("func", "rmdir").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
  1043  
  1044  	// Initialize the common fields of the NSReq
  1045  	rq, err := c.initNSRequest(ctx, auth)
  1046  	if err != nil {
  1047  		return err
  1048  	}
  1049  
  1050  	msg := new(erpc.NSRequest_RmRequest)
  1051  
  1052  	msg.Id = new(erpc.MDId)
  1053  	msg.Id.Path = []byte(path)
  1054  	msg.Recursive = true
  1055  	msg.Norecycle = noRecycle
  1056  
  1057  	rq.Command = &erpc.NSRequest_Rm{Rm: msg}
  1058  
  1059  	// Now send the req and see what happens
  1060  	resp, err := c.cl.Exec(ctx, rq)
  1061  	e := c.getRespError(resp, err)
  1062  	if e != nil {
  1063  		log.Error().Str("func", "rmdir").Str("path", path).Str("err", e.Error()).Msg("")
  1064  		return e
  1065  	}
  1066  
  1067  	if resp == nil {
  1068  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' path: '%s'", auth.Role.UID, path))
  1069  	}
  1070  
  1071  	log.Debug().Str("func", "rmdir").Str("path", path).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
  1072  
  1073  	return err
  1074  }
  1075  
  1076  // Remove removes the resource at the given path
  1077  func (c *Client) Remove(ctx context.Context, auth eosclient.Authorization, path string, noRecycle bool) error {
  1078  	log := appctx.GetLogger(ctx)
  1079  	log.Info().Str("func", "Remove").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
  1080  
  1081  	nfo, err := c.GetFileInfoByPath(ctx, auth, path)
  1082  	if err != nil {
  1083  		log.Warn().Err(err).Str("func", "Remove").Str("path", path).Str("err", err.Error())
  1084  		return err
  1085  	}
  1086  
  1087  	if nfo.IsDir {
  1088  		return c.rmdir(ctx, auth, path, noRecycle)
  1089  	}
  1090  
  1091  	return c.rm(ctx, auth, path, noRecycle)
  1092  }
  1093  
  1094  // Rename renames the resource referenced by oldPath to newPath
  1095  func (c *Client) Rename(ctx context.Context, auth eosclient.Authorization, oldPath, newPath string) error {
  1096  	log := appctx.GetLogger(ctx)
  1097  	log.Info().Str("func", "Rename").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("oldPath", oldPath).Str("newPath", newPath).Msg("")
  1098  
  1099  	// Initialize the common fields of the NSReq
  1100  	rq, err := c.initNSRequest(ctx, auth)
  1101  	if err != nil {
  1102  		return err
  1103  	}
  1104  
  1105  	msg := new(erpc.NSRequest_RenameRequest)
  1106  
  1107  	msg.Id = new(erpc.MDId)
  1108  	msg.Id.Path = []byte(oldPath)
  1109  	msg.Target = []byte(newPath)
  1110  	rq.Command = &erpc.NSRequest_Rename{Rename: msg}
  1111  
  1112  	// Now send the req and see what happens
  1113  	resp, err := c.cl.Exec(ctx, rq)
  1114  	e := c.getRespError(resp, err)
  1115  	if e != nil {
  1116  		log.Error().Str("func", "Rename").Str("oldPath", oldPath).Str("newPath", newPath).Str("err", e.Error()).Msg("")
  1117  		return e
  1118  	}
  1119  
  1120  	if resp == nil {
  1121  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' oldpath: '%s' newpath: '%s'", auth.Role.UID, oldPath, newPath))
  1122  	}
  1123  
  1124  	log.Debug().Str("func", "Rename").Str("oldPath", oldPath).Str("newPath", newPath).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
  1125  
  1126  	return err
  1127  
  1128  }
  1129  
  1130  // List the contents of the directory given by path
  1131  func (c *Client) List(ctx context.Context, auth eosclient.Authorization, dpath string) ([]*eosclient.FileInfo, error) {
  1132  	log := appctx.GetLogger(ctx)
  1133  	log.Info().Str("func", "List").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("dpath", dpath).Msg("")
  1134  
  1135  	// Stuff filename, uid, gid into the FindRequest type
  1136  	fdrq := new(erpc.FindRequest)
  1137  	fdrq.Maxdepth = 1
  1138  	fdrq.Type = erpc.TYPE_LISTING
  1139  	fdrq.Id = new(erpc.MDId)
  1140  	fdrq.Id.Path = []byte(dpath)
  1141  
  1142  	fdrq.Role = new(erpc.RoleId)
  1143  
  1144  	uidInt, err := strconv.ParseUint(auth.Role.UID, 10, 64)
  1145  	if err != nil {
  1146  		return nil, err
  1147  	}
  1148  	gidInt, err := strconv.ParseUint(auth.Role.GID, 10, 64)
  1149  	if err != nil {
  1150  		return nil, err
  1151  	}
  1152  	fdrq.Role.Uid = uidInt
  1153  	fdrq.Role.Gid = gidInt
  1154  
  1155  	fdrq.Authkey = c.opt.Authkey
  1156  
  1157  	// Now send the req and see what happens
  1158  	resp, err := c.cl.Find(context.Background(), fdrq)
  1159  	if err != nil {
  1160  		log.Error().Err(err).Str("func", "List").Str("path", dpath).Str("err", err.Error()).Msg("grpc response")
  1161  
  1162  		return nil, err
  1163  	}
  1164  
  1165  	var mylst []*eosclient.FileInfo
  1166  	var parent *eosclient.FileInfo
  1167  	i := 0
  1168  	for {
  1169  		rsp, err := resp.Recv()
  1170  		if err != nil {
  1171  			if err == io.EOF {
  1172  				log.Debug().Str("path", dpath).Int("nitems", i).Msg("OK, no more items, clean exit")
  1173  				break
  1174  			}
  1175  
  1176  			// We got an error while reading items. We log this as an error and we return
  1177  			// the items we have
  1178  			log.Error().Err(err).Str("func", "List").Int("nitems", i).Str("path", dpath).Str("got err from EOS", err.Error()).Msg("")
  1179  			if i > 0 {
  1180  				log.Error().Str("path", dpath).Int("nitems", i).Msg("No more items, dirty exit")
  1181  				return mylst, nil
  1182  			}
  1183  		}
  1184  
  1185  		if rsp == nil {
  1186  			log.Error().Int("nitems", i).Err(err).Str("func", "List").Str("path", dpath).Str("err", "rsp is nil").Msg("grpc response")
  1187  			return nil, errtypes.NotFound(dpath)
  1188  		}
  1189  
  1190  		log.Debug().Str("func", "List").Str("path", dpath).Str("item resp:", fmt.Sprintf("%#v", rsp)).Msg("grpc response")
  1191  
  1192  		myitem, err := c.grpcMDResponseToFileInfo(rsp)
  1193  		if err != nil {
  1194  			log.Error().Err(err).Str("func", "List").Str("path", dpath).Str("could not convert item:", fmt.Sprintf("%#v", rsp)).Str("err", err.Error()).Msg("")
  1195  
  1196  			return nil, err
  1197  		}
  1198  
  1199  		i++
  1200  		// The first item is the directory itself... skip
  1201  		if i == 1 {
  1202  			parent = myitem
  1203  			log.Debug().Str("func", "List").Str("path", dpath).Str("skipping first item resp:", fmt.Sprintf("%#v", rsp)).Msg("grpc response")
  1204  			continue
  1205  		}
  1206  
  1207  		mylst = append(mylst, myitem)
  1208  	}
  1209  
  1210  	if parent.SysACL != nil {
  1211  
  1212  		for _, info := range mylst {
  1213  			if !info.IsDir && parent != nil {
  1214  				if info.SysACL == nil {
  1215  					log.Warn().Str("func", "List").Str("path", dpath).Str("SysACL is nil, taking parent", "").Msg("grpc response")
  1216  					info.SysACL.Entries = parent.SysACL.Entries
  1217  				} else {
  1218  					info.SysACL.Entries = append(info.SysACL.Entries, parent.SysACL.Entries...)
  1219  				}
  1220  			}
  1221  		}
  1222  	}
  1223  
  1224  	return mylst, nil
  1225  
  1226  }
  1227  
  1228  // Read reads a file from the mgm and returns a handle to read it
  1229  // This handle could be directly the body of the response or a local tmp file
  1230  // returning a handle to the body is nice, yet it gives less control on the transaction
  1231  // itself, e.g. strange timeouts or TCP issues may be more difficult to trace
  1232  // Let's consider this experimental for the moment, maybe I'll like to add a config
  1233  // parameter to choose between the two behaviours
  1234  func (c *Client) Read(ctx context.Context, auth eosclient.Authorization, path string) (io.ReadCloser, error) {
  1235  	log := appctx.GetLogger(ctx)
  1236  	log.Info().Str("func", "Read").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
  1237  
  1238  	var localTarget string
  1239  	var err error
  1240  	var localfile io.WriteCloser
  1241  	localfile = nil
  1242  
  1243  	if c.opt.ReadUsesLocalTemp {
  1244  		rand := "eosread-" + uuid.New().String()
  1245  		localTarget := fmt.Sprintf("%s/%s", c.opt.CacheDirectory, rand)
  1246  		defer os.RemoveAll(localTarget)
  1247  
  1248  		log.Info().Str("func", "Read").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Str("tempfile", localTarget).Msg("")
  1249  		localfile, err = os.Create(localTarget)
  1250  		if err != nil {
  1251  			log.Error().Str("func", "Read").Str("path", path).Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("err", err.Error()).Msg("")
  1252  			return nil, errtypes.InternalError(fmt.Sprintf("can't open local temp file '%s'", localTarget))
  1253  		}
  1254  	}
  1255  
  1256  	bodystream, err := c.httpcl.GETFile(ctx, "", auth, path, localfile)
  1257  	if err != nil {
  1258  		log.Error().Str("func", "Read").Str("path", path).Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("err", err.Error()).Msg("")
  1259  		return nil, errtypes.InternalError(fmt.Sprintf("can't GET local cache file '%s'", localTarget))
  1260  	}
  1261  
  1262  	return bodystream, nil
  1263  	// return os.Open(localTarget)
  1264  }
  1265  
  1266  // Write writes a file to the mgm
  1267  // Somehow the same considerations as Read apply
  1268  func (c *Client) Write(ctx context.Context, auth eosclient.Authorization, path string, stream io.ReadCloser) error {
  1269  	log := appctx.GetLogger(ctx)
  1270  	log.Info().Str("func", "Write").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Msg("")
  1271  	var length int64
  1272  	length = -1
  1273  
  1274  	if c.opt.WriteUsesLocalTemp {
  1275  		fd, err := os.CreateTemp(c.opt.CacheDirectory, "eoswrite-")
  1276  		if err != nil {
  1277  			return err
  1278  		}
  1279  		defer fd.Close()
  1280  		defer os.RemoveAll(fd.Name())
  1281  
  1282  		log.Info().Str("func", "Write").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Str("tempfile", fd.Name()).Msg("")
  1283  		// copy stream to local temp file
  1284  		length, err = io.Copy(fd, stream)
  1285  		if err != nil {
  1286  			return err
  1287  		}
  1288  
  1289  		wfd, err := os.Open(fd.Name())
  1290  		if err != nil {
  1291  			return err
  1292  		}
  1293  		defer wfd.Close()
  1294  		defer os.RemoveAll(fd.Name())
  1295  
  1296  		return c.httpcl.PUTFile(ctx, "", auth, path, wfd, length)
  1297  	}
  1298  
  1299  	return c.httpcl.PUTFile(ctx, "", auth, path, stream, length)
  1300  
  1301  	// return c.httpcl.PUTFile(ctx, remoteuser, auth, urlpathng, stream)
  1302  	// return c.WriteFile(ctx, uid, gid, path, fd.Name())
  1303  }
  1304  
  1305  // WriteFile writes an existing file to the mgm. Old xrdcp utility
  1306  func (c *Client) WriteFile(ctx context.Context, auth eosclient.Authorization, path, source string) error {
  1307  
  1308  	log := appctx.GetLogger(ctx)
  1309  	log.Info().Str("func", "WriteFile").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Str("source", source).Msg("")
  1310  
  1311  	xrdPath := fmt.Sprintf("%s//%s", c.opt.URL, path)
  1312  	cmd := exec.CommandContext(ctx, c.opt.XrdcopyBinary, "--nopbar", "--silent", "-f", source, xrdPath, fmt.Sprintf("-ODeos.ruid=%s&eos.rgid=%s", auth.Role.UID, auth.Role.GID))
  1313  	_, _, err := c.execute(ctx, cmd)
  1314  	return err
  1315  
  1316  }
  1317  
  1318  // ListDeletedEntries returns a list of the deleted entries.
  1319  func (c *Client) ListDeletedEntries(ctx context.Context, auth eosclient.Authorization) ([]*eosclient.DeletedEntry, error) {
  1320  	log := appctx.GetLogger(ctx)
  1321  	log.Info().Str("func", "ListDeletedEntries").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Msg("")
  1322  
  1323  	// Initialize the common fields of the NSReq
  1324  	rq, err := c.initNSRequest(ctx, auth)
  1325  	if err != nil {
  1326  		return nil, err
  1327  	}
  1328  
  1329  	msg := new(erpc.NSRequest_RecycleRequest)
  1330  	msg.Cmd = erpc.NSRequest_RecycleRequest_RECYCLE_CMD(erpc.NSRequest_RecycleRequest_RECYCLE_CMD_value["LIST"])
  1331  
  1332  	rq.Command = &erpc.NSRequest_Recycle{Recycle: msg}
  1333  
  1334  	// Now send the req and see what happens
  1335  	resp, err := c.cl.Exec(context.Background(), rq)
  1336  	e := c.getRespError(resp, err)
  1337  	if e != nil {
  1338  		log.Error().Str("err", e.Error()).Msg("")
  1339  		return nil, e
  1340  	}
  1341  
  1342  	if resp == nil {
  1343  		return nil, errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s'", auth.Role.UID))
  1344  	}
  1345  
  1346  	if resp.GetError() != nil {
  1347  		log.Error().Str("func", "ListDeletedEntries").Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp")
  1348  	} else {
  1349  		log.Debug().Str("func", "ListDeletedEntries").Str("info:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
  1350  	}
  1351  	// TODO(labkode): add protection if slave is configured and alive to count how many files are in the trashbin before
  1352  	// triggering the recycle ls call that could break the instance because of unavailable memory.
  1353  	// FF: I agree with labkode, if we think we may have memory problems then the semantics of the grpc call`and
  1354  	// the semantics if this func will have to change. For now this is not foreseen
  1355  
  1356  	ret := make([]*eosclient.DeletedEntry, 0)
  1357  	for _, f := range resp.Recycle.Recycles {
  1358  		if f == nil {
  1359  			log.Info().Msg("nil item in response")
  1360  			continue
  1361  		}
  1362  
  1363  		entry := &eosclient.DeletedEntry{
  1364  			RestorePath:   string(f.Id.Path),
  1365  			RestoreKey:    f.Key,
  1366  			Size:          f.Size,
  1367  			DeletionMTime: f.Dtime.Sec,
  1368  			IsDir:         (f.Type == erpc.NSResponse_RecycleResponse_RecycleInfo_TREE),
  1369  		}
  1370  
  1371  		ret = append(ret, entry)
  1372  	}
  1373  
  1374  	return ret, nil
  1375  }
  1376  
  1377  // RestoreDeletedEntry restores a deleted entry.
  1378  func (c *Client) RestoreDeletedEntry(ctx context.Context, auth eosclient.Authorization, key string) error {
  1379  	log := appctx.GetLogger(ctx)
  1380  	log.Info().Str("func", "RestoreDeletedEntries").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("key", key).Msg("")
  1381  
  1382  	// Initialize the common fields of the NSReq
  1383  	rq, err := c.initNSRequest(ctx, auth)
  1384  	if err != nil {
  1385  		return err
  1386  	}
  1387  
  1388  	msg := new(erpc.NSRequest_RecycleRequest)
  1389  	msg.Cmd = erpc.NSRequest_RecycleRequest_RECYCLE_CMD(erpc.NSRequest_RecycleRequest_RECYCLE_CMD_value["RESTORE"])
  1390  
  1391  	msg.Key = key
  1392  
  1393  	rq.Command = &erpc.NSRequest_Recycle{Recycle: msg}
  1394  
  1395  	// Now send the req and see what happens
  1396  	resp, err := c.cl.Exec(context.Background(), rq)
  1397  	e := c.getRespError(resp, err)
  1398  	if e != nil {
  1399  		log.Error().Str("func", "RestoreDeletedEntries").Str("key", key).Str("err", e.Error()).Msg("")
  1400  		return e
  1401  	}
  1402  
  1403  	if resp == nil {
  1404  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' key: '%s'", auth.Role.UID, key))
  1405  	}
  1406  
  1407  	if resp.GetError() != nil {
  1408  		log.Error().Str("func", "RestoreDeletedEntries").Str("key", key).Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("EOS negative resp")
  1409  	} else {
  1410  		log.Info().Str("func", "RestoreDeletedEntries").Str("key", key).Str("resp:", fmt.Sprintf("%#v", resp)).Msg("grpc response")
  1411  	}
  1412  	return err
  1413  }
  1414  
  1415  // PurgeDeletedEntries purges all entries from the recycle bin.
  1416  func (c *Client) PurgeDeletedEntries(ctx context.Context, auth eosclient.Authorization) error {
  1417  	log := appctx.GetLogger(ctx)
  1418  	log.Info().Str("func", "PurgeDeletedEntries").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Msg("")
  1419  
  1420  	// Initialize the common fields of the NSReq
  1421  	rq, err := c.initNSRequest(ctx, auth)
  1422  	if err != nil {
  1423  		return err
  1424  	}
  1425  
  1426  	msg := new(erpc.NSRequest_RecycleRequest)
  1427  	msg.Cmd = erpc.NSRequest_RecycleRequest_RECYCLE_CMD(erpc.NSRequest_RecycleRequest_RECYCLE_CMD_value["PURGE"])
  1428  
  1429  	rq.Command = &erpc.NSRequest_Recycle{Recycle: msg}
  1430  
  1431  	// Now send the req and see what happens
  1432  	resp, err := c.cl.Exec(context.Background(), rq)
  1433  	e := c.getRespError(resp, err)
  1434  	if e != nil {
  1435  		log.Error().Str("func", "PurgeDeletedEntries").Str("err", e.Error()).Msg("")
  1436  		return e
  1437  	}
  1438  
  1439  	if resp == nil {
  1440  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' ", auth.Role.UID))
  1441  	}
  1442  
  1443  	log.Info().Str("func", "PurgeDeletedEntries").Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("grpc response")
  1444  
  1445  	return err
  1446  }
  1447  
  1448  // ListVersions list all the versions for a given file.
  1449  func (c *Client) ListVersions(ctx context.Context, auth eosclient.Authorization, p string) ([]*eosclient.FileInfo, error) {
  1450  	log := appctx.GetLogger(ctx)
  1451  	log.Info().Str("func", "ListVersions").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("p", p).Msg("")
  1452  
  1453  	versionFolder := getVersionFolder(p)
  1454  	finfos, err := c.List(ctx, auth, versionFolder)
  1455  	if err != nil {
  1456  		// we send back an empty list
  1457  		return []*eosclient.FileInfo{}, nil
  1458  	}
  1459  	return finfos, nil
  1460  }
  1461  
  1462  // RollbackToVersion rollbacks a file to a previous version.
  1463  func (c *Client) RollbackToVersion(ctx context.Context, auth eosclient.Authorization, path, version string) error {
  1464  
  1465  	log := appctx.GetLogger(ctx)
  1466  	log.Info().Str("func", "RollbackToVersion").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("path", path).Str("version", version).Msg("")
  1467  
  1468  	// Initialize the common fields of the NSReq
  1469  	rq, err := c.initNSRequest(ctx, auth)
  1470  	if err != nil {
  1471  		return err
  1472  	}
  1473  
  1474  	msg := new(erpc.NSRequest_VersionRequest)
  1475  	msg.Cmd = erpc.NSRequest_VersionRequest_VERSION_CMD(erpc.NSRequest_VersionRequest_VERSION_CMD_value["GRAB"])
  1476  	msg.Id = new(erpc.MDId)
  1477  	msg.Id.Path = []byte(path)
  1478  	msg.Grabversion = version
  1479  
  1480  	rq.Command = &erpc.NSRequest_Version{Version: msg}
  1481  
  1482  	// Now send the req and see what happens
  1483  	resp, err := c.cl.Exec(context.Background(), rq)
  1484  	e := c.getRespError(resp, err)
  1485  	if e != nil {
  1486  		log.Error().Str("func", "RollbackToVersion").Str("err", e.Error()).Msg("")
  1487  		return e
  1488  	}
  1489  
  1490  	if resp == nil {
  1491  		return errtypes.InternalError(fmt.Sprintf("nil response for uid: '%s' ", auth.Role.UID))
  1492  	}
  1493  
  1494  	log.Info().Str("func", "RollbackToVersion").Int64("errcode", resp.GetError().Code).Str("errmsg", resp.GetError().Msg).Msg("grpc response")
  1495  
  1496  	return err
  1497  
  1498  }
  1499  
  1500  // ReadVersion reads the version for the given file.
  1501  func (c *Client) ReadVersion(ctx context.Context, auth eosclient.Authorization, p, version string) (io.ReadCloser, error) {
  1502  	log := appctx.GetLogger(ctx)
  1503  	log.Info().Str("func", "ReadVersion").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("p", p).Str("version", version).Msg("")
  1504  
  1505  	versionFile := path.Join(getVersionFolder(p), version)
  1506  	return c.Read(ctx, auth, versionFile)
  1507  }
  1508  
  1509  // GenerateToken returns a token on behalf of the resource owner to be used by lightweight accounts
  1510  func (c *Client) GenerateToken(ctx context.Context, auth eosclient.Authorization, path string, a *acl.Entry) (string, error) {
  1511  	return "", errtypes.NotSupported("TODO")
  1512  }
  1513  
  1514  func (c *Client) getVersionFolderInode(ctx context.Context, auth eosclient.Authorization, p string) (uint64, error) {
  1515  	log := appctx.GetLogger(ctx)
  1516  	log.Info().Str("func", "getVersionFolderInode").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("p", p).Msg("")
  1517  
  1518  	versionFolder := getVersionFolder(p)
  1519  	md, err := c.GetFileInfoByPath(ctx, auth, versionFolder)
  1520  	if err != nil {
  1521  		if err = c.CreateDir(ctx, auth, versionFolder); err != nil {
  1522  			return 0, err
  1523  		}
  1524  		md, err = c.GetFileInfoByPath(ctx, auth, versionFolder)
  1525  		if err != nil {
  1526  			return 0, err
  1527  		}
  1528  	}
  1529  	return md.Inode, nil
  1530  }
  1531  
  1532  func (c *Client) getFileInfoFromVersion(ctx context.Context, auth eosclient.Authorization, p string) (*eosclient.FileInfo, error) {
  1533  	log := appctx.GetLogger(ctx)
  1534  	log.Info().Str("func", "getFileInfoFromVersion").Str("uid,gid", auth.Role.UID+","+auth.Role.GID).Str("p", p).Msg("")
  1535  
  1536  	file := getFileFromVersionFolder(p)
  1537  	md, err := c.GetFileInfoByPath(ctx, auth, file)
  1538  	if err != nil {
  1539  		return nil, err
  1540  	}
  1541  	return md, nil
  1542  }
  1543  
  1544  func isVersionFolder(p string) bool {
  1545  	return strings.HasPrefix(path.Base(p), versionPrefix)
  1546  }
  1547  
  1548  func getVersionFolder(p string) string {
  1549  	return path.Join(path.Dir(p), versionPrefix+path.Base(p))
  1550  }
  1551  
  1552  func getFileFromVersionFolder(p string) string {
  1553  	return path.Join(path.Dir(p), strings.TrimPrefix(path.Base(p), versionPrefix))
  1554  }
  1555  
  1556  func (c *Client) grpcMDResponseToFileInfo(st *erpc.MDResponse) (*eosclient.FileInfo, error) {
  1557  	if st.Cmd == nil && st.Fmd == nil {
  1558  		return nil, errors.Wrap(errtypes.NotSupported(""), "Invalid response (st.Cmd and st.Fmd are nil)")
  1559  	}
  1560  	fi := new(eosclient.FileInfo)
  1561  
  1562  	if st.Type == erpc.TYPE_CONTAINER {
  1563  		fi.IsDir = true
  1564  		fi.Inode = st.Fmd.Inode
  1565  		fi.FID = st.Cmd.ParentId
  1566  		fi.UID = st.Cmd.Uid
  1567  		fi.GID = st.Cmd.Gid
  1568  		fi.MTimeSec = st.Cmd.Mtime.Sec
  1569  		fi.ETag = st.Cmd.Etag
  1570  		fi.File = path.Clean(string(st.Cmd.Path))
  1571  
  1572  		fi.Attrs = make(map[string]string)
  1573  		for k, v := range st.Cmd.Xattrs {
  1574  			fi.Attrs[strings.TrimPrefix(k, "user.")] = string(v)
  1575  		}
  1576  
  1577  		fi.Size = uint64(st.Cmd.TreeSize)
  1578  
  1579  		log.Debug().Str("stat info - path", fi.File).Uint64("inode", fi.Inode).Uint64("uid", fi.UID).Uint64("gid", fi.GID).Str("etag", fi.ETag).Msg("grpc response")
  1580  	} else {
  1581  		fi.Inode = st.Fmd.Inode
  1582  		fi.FID = st.Fmd.ContId
  1583  		fi.UID = st.Fmd.Uid
  1584  		fi.GID = st.Fmd.Gid
  1585  		fi.MTimeSec = st.Fmd.Mtime.Sec
  1586  		fi.ETag = st.Fmd.Etag
  1587  		fi.File = path.Clean(string(st.Fmd.Path))
  1588  
  1589  		fi.Attrs = make(map[string]string)
  1590  		for k, v := range st.Fmd.Xattrs {
  1591  			fi.Attrs[strings.TrimPrefix(k, "user.")] = string(v)
  1592  		}
  1593  
  1594  		fi.Size = st.Fmd.Size
  1595  
  1596  		if st.Fmd.Checksum != nil {
  1597  			xs := &eosclient.Checksum{
  1598  				XSSum:  hex.EncodeToString(st.Fmd.Checksum.Value),
  1599  				XSType: st.Fmd.Checksum.Type,
  1600  			}
  1601  			fi.XS = xs
  1602  
  1603  			log.Debug().Str("stat info - path", fi.File).Uint64("inode", fi.Inode).Uint64("uid", fi.UID).Uint64("gid", fi.GID).Str("etag", fi.ETag).Str("checksum", fi.XS.XSType+":"+fi.XS.XSSum).Msg("grpc response")
  1604  		}
  1605  	}
  1606  	return fi, nil
  1607  }
  1608  
  1609  // exec executes the command and returns the stdout, stderr and return code
  1610  func (c *Client) execute(ctx context.Context, cmd *exec.Cmd) (string, string, error) {
  1611  	log := appctx.GetLogger(ctx)
  1612  
  1613  	outBuf := &bytes.Buffer{}
  1614  	errBuf := &bytes.Buffer{}
  1615  	cmd.Stdout = outBuf
  1616  	cmd.Stderr = errBuf
  1617  	cmd.Env = []string{
  1618  		"EOS_MGM_URL=" + c.opt.URL,
  1619  	}
  1620  
  1621  	if c.opt.UseKeytab {
  1622  		cmd.Env = append(cmd.Env, "XrdSecPROTOCOL="+c.opt.SecProtocol)
  1623  		cmd.Env = append(cmd.Env, "XrdSecSSSKT="+c.opt.Keytab)
  1624  	}
  1625  
  1626  	err := cmd.Run()
  1627  
  1628  	var exitStatus int
  1629  	if exiterr, ok := err.(*exec.ExitError); ok {
  1630  		// The program has exited with an exit code != 0
  1631  		// This works on both Unix and Windows. Although package
  1632  		// syscall is generally platform dependent, WaitStatus is
  1633  		// defined for both Unix and Windows and in both cases has
  1634  		// an ExitStatus() method with the same signature.
  1635  		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
  1636  
  1637  			exitStatus = status.ExitStatus()
  1638  			switch exitStatus {
  1639  			case 0:
  1640  				err = nil
  1641  			case 2:
  1642  				err = errtypes.NotFound(errBuf.String())
  1643  			}
  1644  		}
  1645  	}
  1646  
  1647  	args := fmt.Sprintf("%s", cmd.Args)
  1648  	env := fmt.Sprintf("%s", cmd.Env)
  1649  	log.Info().Str("args", args).Str("env", env).Int("exit", exitStatus).Msg("eos cmd")
  1650  
  1651  	if err != nil && exitStatus != 2 { // don't wrap the errtypes.NotFoundError
  1652  		err = errors.Wrap(err, "eosclient: error while executing command")
  1653  	}
  1654  
  1655  	return outBuf.String(), errBuf.String(), err
  1656  }