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 }