github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/util/fs/mount/mount.go (about) 1 // Copyright (c) 2018-2019, Sylabs Inc. All rights reserved. 2 // This software is licensed under a 3-clause BSD license. Please consult the 3 // LICENSE.md file distributed with the sources of this project regarding your 4 // rights to use or distribute this software. 5 6 package mount 7 8 import ( 9 "fmt" 10 "path/filepath" 11 "strings" 12 "syscall" 13 14 "github.com/sylabs/singularity/pkg/util/fs/proc" 15 16 specs "github.com/opencontainers/runtime-spec/specs-go" 17 ) 18 19 type mountError string 20 21 func (e mountError) Error() string { return string(e) } 22 23 const ( 24 // ErrMountExists indicates a duplicated mount being asked for 25 ErrMountExists = mountError("destination is already in the mount point list") 26 ) 27 28 var mountFlags = []struct { 29 option string 30 flag uintptr 31 }{ 32 {"acl", 0}, 33 {"async", syscall.MS_ASYNC}, 34 {"atime", 0}, 35 {"bind", syscall.MS_BIND}, 36 {"defaults", 0}, 37 {"dev", 0}, 38 {"diratime", 0}, 39 {"dirsync", syscall.MS_DIRSYNC}, 40 {"exec", 0}, 41 {"iversion", 0}, 42 {"lazytime", 0}, 43 {"loud", 0}, 44 {"mand", syscall.MS_MANDLOCK}, 45 {"noacl", 0}, 46 {"noatime", syscall.MS_NOATIME}, 47 {"nodev", syscall.MS_NODEV}, 48 {"nodiratime", syscall.MS_NODIRATIME}, 49 {"noexec", syscall.MS_NOEXEC}, 50 {"noiversion", 0}, 51 {"nolazytime", 0}, 52 {"nomand", 0}, 53 {"norelatime", 0}, 54 {"nostrictatime", 0}, 55 {"nosuid", syscall.MS_NOSUID}, 56 {"private", syscall.MS_PRIVATE}, 57 {"rbind", syscall.MS_BIND | syscall.MS_REC}, 58 {"rprivate", syscall.MS_PRIVATE | syscall.MS_REC}, 59 {"rslave", syscall.MS_SLAVE | syscall.MS_REC}, 60 {"rshared", syscall.MS_SHARED | syscall.MS_REC}, 61 {"runbindable", syscall.MS_UNBINDABLE | syscall.MS_REC}, 62 {"relatime", syscall.MS_RELATIME}, 63 {"remount", syscall.MS_REMOUNT}, 64 {"ro", syscall.MS_RDONLY}, 65 {"rw", 0}, 66 {"shared", syscall.MS_SHARED}, 67 {"slave", syscall.MS_SLAVE}, 68 {"silent", syscall.MS_SILENT}, 69 {"strictatime", syscall.MS_STRICTATIME}, 70 {"suid", 0}, 71 {"sync", syscall.MS_SYNCHRONOUS}, 72 {"unbindable", syscall.MS_UNBINDABLE}, 73 } 74 75 type fsContext struct { 76 context bool 77 } 78 79 // AuthorizedTag defines the tag type 80 type AuthorizedTag string 81 82 const ( 83 // SessionTag defines tag for session directory 84 SessionTag AuthorizedTag = "sessiondir" 85 // RootfsTag defines tag for container root filesystem 86 RootfsTag = "rootfs" 87 // PreLayerTag defines tag to prepare overlay/underlay layer 88 PreLayerTag = "prelayer" 89 // LayerTag defines tag for overlay/underlay final mount point 90 LayerTag = "layer" 91 // DevTag defines tag for dev related mount point 92 DevTag = "dev" 93 // HostfsTag defines tag for host filesystem mount point 94 HostfsTag = "hostfs" 95 // BindsTag defines tag for bind path 96 BindsTag = "binds" 97 // KernelTag defines tag for kernel related mount point (proc, sysfs) 98 KernelTag = "kernel" 99 // HomeTag defines tag for home directory mount point 100 HomeTag = "home" 101 // TmpTag defines tag for temporary filesystem mount points (/tmp, /var/tmp) 102 TmpTag = "tmp" 103 // ScratchTag defines tag for scratch mount points 104 ScratchTag = "scratch" 105 // CwdTag defines tag for current working directory mount point 106 CwdTag = "cwd" 107 // FilesTag defines tag for file mount points (passwd, group ...) 108 FilesTag = "files" 109 // UserbindsTag defines tag for user bind mount points 110 UserbindsTag = "userbinds" 111 // OtherTag defines tag for other mount points that can't be classified 112 OtherTag = "other" 113 // FinalTag defines tag for mount points to mount/remount at the end of mount process 114 FinalTag = "final" 115 ) 116 117 var authorizedTags = map[AuthorizedTag]struct { 118 multiPoint bool 119 order int 120 }{ 121 SessionTag: {false, 0}, 122 RootfsTag: {false, 1}, 123 PreLayerTag: {true, 2}, 124 LayerTag: {false, 3}, 125 DevTag: {true, 4}, 126 HostfsTag: {true, 5}, 127 BindsTag: {true, 6}, 128 KernelTag: {true, 7}, 129 HomeTag: {false, 8}, 130 TmpTag: {true, 9}, 131 ScratchTag: {true, 10}, 132 CwdTag: {false, 11}, 133 FilesTag: {true, 12}, 134 UserbindsTag: {true, 13}, 135 OtherTag: {true, 14}, 136 FinalTag: {true, 15}, 137 } 138 139 var authorizedImage = map[string]fsContext{ 140 "ext3": {true}, 141 "squashfs": {true}, 142 } 143 144 var authorizedFS = map[string]fsContext{ 145 "overlay": {true}, 146 "tmpfs": {true}, 147 "ramfs": {true}, 148 "devpts": {true}, 149 "sysfs": {false}, 150 "proc": {false}, 151 "mqueue": {false}, 152 "cgroup": {false}, 153 } 154 155 var internalOptions = []string{"loop", "offset", "sizelimit"} 156 157 // Point describes a mount point 158 type Point struct { 159 specs.Mount 160 InternalOptions []string `json:"internalOptions"` 161 } 162 163 // Points defines and stores a set of mount points by tag 164 type Points struct { 165 context string 166 points map[AuthorizedTag][]Point 167 } 168 169 // ConvertOptions converts an options string into a pair of mount flags and mount options 170 func ConvertOptions(options []string) (uintptr, []string) { 171 var flags uintptr 172 finalOpt := []string{} 173 isFlag := false 174 175 for _, option := range options { 176 optionTrim := strings.TrimSpace(option) 177 for _, flag := range mountFlags { 178 if flag.option == optionTrim { 179 flags |= flag.flag 180 isFlag = true 181 break 182 } 183 } 184 if !isFlag { 185 finalOpt = append(finalOpt, optionTrim) 186 } 187 isFlag = false 188 } 189 return flags, finalOpt 190 } 191 192 // ConvertSpec converts an OCI Mount spec into an importable mount points list 193 func ConvertSpec(mounts []specs.Mount) (map[AuthorizedTag][]Point, error) { 194 var tag AuthorizedTag 195 196 points := make(map[AuthorizedTag][]Point) 197 for _, m := range mounts { 198 var options []string 199 var propagationOption string 200 var err error 201 source := m.Source 202 mountType := m.Type 203 204 tag = "" 205 if mountType != "" && mountType != "bind" && mountType != "none" { 206 if _, ok := authorizedFS[mountType]; !ok { 207 return points, fmt.Errorf("%s filesystem type is not authorized", mountType) 208 } 209 if has, err := proc.HasFilesystem(mountType); err != nil || !has { 210 return points, fmt.Errorf("%s filesystem not supported", mountType) 211 } 212 tag = KernelTag 213 } else { 214 source, err = filepath.Abs(m.Source) 215 if err != nil { 216 return points, fmt.Errorf("failed to determine absolute path for %s: %s", m.Source, err) 217 } 218 tag = UserbindsTag 219 mountType = "" 220 } 221 222 for _, opt := range m.Options { 223 switch opt { 224 case "shared", 225 "rshared", 226 "slave", 227 "rslave", 228 "private", 229 "rprivate", 230 "unbindable", 231 "runbindable": 232 propagationOption = opt 233 default: 234 options = append(options, opt) 235 } 236 } 237 238 points[tag] = append(points[tag], Point{ 239 Mount: specs.Mount{ 240 Source: source, 241 Destination: m.Destination, 242 Type: mountType, 243 Options: options, 244 }, 245 }) 246 247 if len(options) > 1 && tag == UserbindsTag { 248 options = append(options, "remount") 249 points[tag] = append(points[tag], Point{ 250 Mount: specs.Mount{ 251 Source: "", 252 Destination: m.Destination, 253 Type: "", 254 Options: options, 255 }, 256 }) 257 } 258 if propagationOption != "" { 259 points[tag] = append(points[tag], Point{ 260 Mount: specs.Mount{ 261 Source: "", 262 Destination: m.Destination, 263 Type: "", 264 Options: []string{propagationOption}, 265 }, 266 }) 267 } 268 } 269 return points, nil 270 } 271 272 // GetTagList returns authorized tags in right order 273 func GetTagList() []AuthorizedTag { 274 tagList := make([]AuthorizedTag, len(authorizedTags)) 275 for k, tag := range authorizedTags { 276 tagList[tag.order] = k 277 } 278 return tagList 279 } 280 281 // GetOffset return offset value for image options 282 func GetOffset(options []string) (uint64, error) { 283 var offset uint64 284 for _, opt := range options { 285 if strings.HasPrefix(opt, "offset=") { 286 fmt.Sscanf(opt, "offset=%d", &offset) 287 return offset, nil 288 } 289 } 290 return 0, fmt.Errorf("offset option not found") 291 } 292 293 // GetSizeLimit returns sizelimit value for image options 294 func GetSizeLimit(options []string) (uint64, error) { 295 var size uint64 296 for _, opt := range options { 297 if strings.HasPrefix(opt, "sizelimit=") { 298 fmt.Sscanf(opt, "sizelimit=%d", &size) 299 return size, nil 300 } 301 } 302 return 0, fmt.Errorf("sizelimit option not found") 303 } 304 305 // HasRemountFlag checks if remount flag is set or not. 306 func HasRemountFlag(flags uintptr) bool { 307 return flags&syscall.MS_REMOUNT != 0 308 } 309 310 // HasPropagationFlag checks if a propagation flag is set or not. 311 func HasPropagationFlag(flags uintptr) bool { 312 return flags&getPropagationFlags() != 0 313 } 314 315 func getPropagationFlags() uintptr { 316 return syscall.MS_UNBINDABLE | syscall.MS_SHARED | syscall.MS_PRIVATE | syscall.MS_SLAVE 317 } 318 319 func (p *Points) init() { 320 if p.points == nil { 321 p.points = make(map[AuthorizedTag][]Point) 322 } 323 } 324 325 func (p *Points) add(tag AuthorizedTag, source string, dest string, fstype string, flags uintptr, options string) error { 326 var bind = false 327 328 p.init() 329 330 mountOpts := []string{} 331 internalOpts := []string{} 332 333 if dest == "" { 334 return fmt.Errorf("mount point must contain a destination") 335 } 336 if !strings.HasPrefix(dest, "/") { 337 return fmt.Errorf("destination must be an absolute path") 338 } 339 if _, ok := authorizedTags[tag]; !ok { 340 return fmt.Errorf("tag %s is not a recognized tag", tag) 341 } 342 if !HasRemountFlag(flags) && !HasPropagationFlag(flags) { 343 present := false 344 for _, point := range p.points[tag] { 345 if point.Destination == dest { 346 present = true 347 break 348 } 349 } 350 if present { 351 return ErrMountExists 352 } 353 354 if len(p.points[tag]) == 1 && !authorizedTags[tag].multiPoint { 355 return fmt.Errorf("tag %s allow only one mount point", tag) 356 } 357 } 358 for i := len(mountFlags) - 1; i >= 0; i-- { 359 flag := mountFlags[i].flag 360 if flag != 0 && flag == (flags&flag) { 361 if bind && flag&syscall.MS_BIND != 0 { 362 continue 363 } 364 mountOpts = append(mountOpts, mountFlags[i].option) 365 if flag&syscall.MS_BIND != 0 { 366 bind = true 367 } 368 } 369 } 370 setContext := true 371 for _, option := range strings.Split(options, ",") { 372 o := strings.TrimSpace(option) 373 if o != "" { 374 keyVal := strings.SplitN(o, "=", 2) 375 if keyVal[0] == "context" { 376 setContext = false 377 } 378 isInternal := false 379 for _, internal := range internalOptions { 380 if keyVal[0] == internal { 381 isInternal = true 382 } 383 } 384 if isInternal == true { 385 internalOpts = append(internalOpts, o) 386 } else { 387 mountOpts = append(mountOpts, o) 388 } 389 } 390 } 391 if fstype != "" && setContext { 392 setContext = authorizedFS[fstype].context 393 } 394 if !bind && setContext && p.context != "" { 395 context := fmt.Sprintf("context=%q", p.context) 396 mountOpts = append(mountOpts, context) 397 } 398 p.points[tag] = append(p.points[tag], Point{ 399 Mount: specs.Mount{ 400 Source: source, 401 Destination: dest, 402 Type: fstype, 403 Options: mountOpts, 404 }, 405 InternalOptions: internalOpts, 406 }) 407 return nil 408 } 409 410 // GetAll returns all registered mount points 411 func (p *Points) GetAll() map[AuthorizedTag][]Point { 412 p.init() 413 return p.points 414 } 415 416 // GetByDest returns registered mount points with the matched destination 417 func (p *Points) GetByDest(dest string) []Point { 418 p.init() 419 mounts := []Point{} 420 for tag := range p.points { 421 for _, point := range p.points[tag] { 422 if point.Destination == dest { 423 mounts = append(mounts, point) 424 } 425 } 426 } 427 return mounts 428 } 429 430 // GetBySource returns registered mount points with the matched source 431 func (p *Points) GetBySource(source string) []Point { 432 p.init() 433 mounts := []Point{} 434 for tag := range p.points { 435 for _, point := range p.points[tag] { 436 if point.Source == source { 437 mounts = append(mounts, point) 438 } 439 } 440 } 441 return mounts 442 } 443 444 // GetByTag returns mount points attached to a tag 445 func (p *Points) GetByTag(tag AuthorizedTag) []Point { 446 p.init() 447 return p.points[tag] 448 } 449 450 // RemoveAll removes all mounts points from list 451 func (p *Points) RemoveAll() { 452 p.init() 453 for tag := range p.points { 454 p.points[tag] = nil 455 } 456 } 457 458 // RemoveByDest removes mount points identified by destination 459 func (p *Points) RemoveByDest(dest string) { 460 p.init() 461 for tag := range p.points { 462 for d := len(p.points[tag]) - 1; d >= 0; d-- { 463 if p.points[tag][d].Destination == dest { 464 p.points[tag] = append(p.points[tag][:d], p.points[tag][d+1:]...) 465 } 466 } 467 } 468 } 469 470 // RemoveBySource removes mount points identified by source 471 func (p *Points) RemoveBySource(source string) { 472 p.init() 473 for tag := range p.points { 474 for d := len(p.points[tag]) - 1; d >= 0; d-- { 475 if p.points[tag][d].Source == source { 476 p.points[tag] = append(p.points[tag][:d], p.points[tag][d+1:]...) 477 } 478 } 479 } 480 } 481 482 // RemoveByTag removes mount points attached to a tag 483 func (p *Points) RemoveByTag(tag AuthorizedTag) { 484 p.init() 485 p.points[tag] = nil 486 } 487 488 // Import imports a mount point list 489 func (p *Points) Import(points map[AuthorizedTag][]Point) error { 490 for tag := range points { 491 for _, point := range points[tag] { 492 var err error 493 var offset uint64 494 var sizelimit uint64 495 496 flags, options := ConvertOptions(point.Options) 497 // check if this is a mount point to remount 498 if HasRemountFlag(flags) { 499 if err = p.AddRemount(tag, point.Destination, flags); err == nil { 500 continue 501 } 502 } 503 if HasPropagationFlag(flags) { 504 if err = p.AddPropagation(tag, point.Destination, flags); err == nil { 505 continue 506 } 507 } 508 // check if this is a bind mount point 509 if flags&syscall.MS_BIND != 0 { 510 if err = p.AddBind(tag, point.Source, point.Destination, flags); err == nil { 511 continue 512 } else { 513 return err 514 } 515 } 516 517 for _, option := range point.InternalOptions { 518 if strings.HasPrefix(option, "offset=") { 519 fmt.Sscanf(option, "offset=%d", &offset) 520 } 521 if strings.HasPrefix(option, "sizelimit=") { 522 fmt.Sscanf(option, "sizelimit=%d", &sizelimit) 523 } 524 } 525 526 // check if this is an image mount point 527 if err = p.AddImage(tag, point.Source, point.Destination, point.Type, flags, offset, sizelimit); err == nil { 528 continue 529 } 530 531 // check if this is a filesystem or overlay mount point 532 if point.Type != "overlay" { 533 if err = p.AddFSWithSource(tag, point.Source, point.Destination, point.Type, flags, strings.Join(options, ",")); err == nil { 534 continue 535 } 536 } else { 537 lowerdir := "" 538 upperdir := "" 539 workdir := "" 540 for _, option := range options { 541 if strings.HasPrefix(option, "lowerdir=") { 542 fmt.Sscanf(option, "lowerdir=%s", &lowerdir) 543 } else if strings.HasPrefix(option, "upperdir=") { 544 fmt.Sscanf(option, "upperdir=%s", &upperdir) 545 } else if strings.HasPrefix(option, "workdir=") { 546 fmt.Sscanf(option, "workdir=%s", &workdir) 547 } 548 } 549 if err = p.AddOverlay(tag, point.Destination, flags, lowerdir, upperdir, workdir); err == nil { 550 continue 551 } 552 } 553 p.RemoveAll() 554 return fmt.Errorf("can't import mount points list: %s", err) 555 } 556 } 557 return nil 558 } 559 560 // ImportFromSpec converts an OCI Mount spec into a mount point list 561 // and imports it 562 func (p *Points) ImportFromSpec(mounts []specs.Mount) error { 563 points, err := ConvertSpec(mounts) 564 if err != nil { 565 return err 566 } 567 return p.Import(points) 568 } 569 570 // AddImage adds an image mount point 571 func (p *Points) AddImage(tag AuthorizedTag, source string, dest string, fstype string, flags uintptr, offset uint64, sizelimit uint64) error { 572 options := "" 573 if source == "" { 574 return fmt.Errorf("an image mount point must contain a source") 575 } 576 if !strings.HasPrefix(source, "/") { 577 return fmt.Errorf("source must be an absolute path") 578 } 579 if flags&(syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_REC) != 0 { 580 return fmt.Errorf("MS_BIND, MS_REC or MS_REMOUNT are not valid flags for image mount points") 581 } 582 if _, ok := authorizedImage[fstype]; !ok { 583 return fmt.Errorf("mount %s image is not authorized", fstype) 584 } 585 if sizelimit == 0 { 586 return fmt.Errorf("invalid image size, zero length") 587 } 588 options = fmt.Sprintf("loop,offset=%d,sizelimit=%d,errors=remount-ro", offset, sizelimit) 589 return p.add(tag, source, dest, fstype, flags, options) 590 } 591 592 // GetAllImages returns a list of all registered image mount points 593 func (p *Points) GetAllImages() []Point { 594 p.init() 595 images := []Point{} 596 for tag := range p.points { 597 for _, point := range p.points[tag] { 598 if _, ok := authorizedImage[point.Type]; ok { 599 images = append(images, point) 600 } 601 } 602 } 603 return images 604 } 605 606 // AddBind adds a bind mount point 607 func (p *Points) AddBind(tag AuthorizedTag, source string, dest string, flags uintptr) error { 608 bindFlags := flags | syscall.MS_BIND 609 options := "" 610 611 if source == "" { 612 return fmt.Errorf("a bind mount point must contain a source") 613 } 614 if !strings.HasPrefix(source, "/") { 615 return fmt.Errorf("source must be an absolute path") 616 } 617 return p.add(tag, source, dest, "", bindFlags, options) 618 } 619 620 // GetAllBinds returns a list of all registered bind mount points 621 func (p *Points) GetAllBinds() []Point { 622 p.init() 623 binds := []Point{} 624 for tag := range p.points { 625 for _, point := range p.points[tag] { 626 for _, option := range point.Options { 627 if option == "bind" || option == "rbind" { 628 binds = append(binds, point) 629 break 630 } 631 } 632 } 633 } 634 return binds 635 } 636 637 // AddOverlay adds an overlay mount point 638 func (p *Points) AddOverlay(tag AuthorizedTag, dest string, flags uintptr, lowerdir string, upperdir string, workdir string) error { 639 if flags&(syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_REC) != 0 { 640 return fmt.Errorf("MS_BIND, MS_REC or MS_REMOUNT are not valid flags for overlay mount points") 641 } 642 if lowerdir == "" { 643 return fmt.Errorf("overlay mount point %s should have at least lowerdir option", dest) 644 } 645 if !strings.HasPrefix(lowerdir, "/") { 646 return fmt.Errorf("lowerdir may contain only an absolute paths") 647 } 648 options := "" 649 if upperdir != "" { 650 if !strings.HasPrefix(upperdir, "/") { 651 return fmt.Errorf("upperdir must be an absolute path") 652 } 653 if workdir == "" { 654 return fmt.Errorf("overlay mount point %s should have workdir option set when used in conjunction with upperdir", dest) 655 } 656 if !strings.HasPrefix(workdir, "/") { 657 return fmt.Errorf("workdir must be an absolute path") 658 } 659 options = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerdir, upperdir, workdir) 660 } else { 661 options = fmt.Sprintf("lowerdir=%s", lowerdir) 662 } 663 return p.add(tag, "overlay", dest, "overlay", flags, options) 664 } 665 666 // GetAllOverlays returns a list of all registered overlay mount points 667 func (p *Points) GetAllOverlays() []Point { 668 p.init() 669 fs := []Point{} 670 for tag := range p.points { 671 for _, point := range p.points[tag] { 672 if point.Type == "overlay" { 673 fs = append(fs, point) 674 } 675 } 676 } 677 return fs 678 } 679 680 // AddFS adds a filesystem mount point 681 func (p *Points) AddFS(tag AuthorizedTag, dest string, fstype string, flags uintptr, options string) error { 682 return p.AddFSWithSource(tag, fstype, dest, fstype, flags, options) 683 } 684 685 // AddFSWithSource adds a filesystem mount point 686 func (p *Points) AddFSWithSource(tag AuthorizedTag, source string, dest string, fstype string, flags uintptr, options string) error { 687 if flags&(syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_REC) != 0 { 688 return fmt.Errorf("MS_BIND, MS_REC or MS_REMOUNT are not valid flags for FS mount points") 689 } 690 if _, ok := authorizedFS[fstype]; !ok { 691 return fmt.Errorf("mount %s file system is not authorized", fstype) 692 } 693 return p.add(tag, source, dest, fstype, flags, options) 694 } 695 696 // GetAllFS returns a list of all registered filesystem mount points 697 func (p *Points) GetAllFS() []Point { 698 p.init() 699 fs := []Point{} 700 for tag := range p.points { 701 for _, point := range p.points[tag] { 702 for fstype := range authorizedFS { 703 if fstype == point.Type && point.Type != "overlay" { 704 fs = append(fs, point) 705 } 706 } 707 } 708 } 709 return fs 710 } 711 712 // AddRemount adds a mount point to remount 713 func (p *Points) AddRemount(tag AuthorizedTag, dest string, flags uintptr) error { 714 remountFlags := (flags &^ getPropagationFlags()) | syscall.MS_REMOUNT 715 return p.add(tag, "", dest, "", remountFlags, "") 716 } 717 718 // AddPropagation adds a mount propagation for mount point 719 func (p *Points) AddPropagation(tag AuthorizedTag, dest string, flags uintptr) error { 720 finalFlags := flags & getPropagationFlags() 721 if !HasPropagationFlag(finalFlags) { 722 return fmt.Errorf("no mount propagation flag found") 723 } 724 if flags&syscall.MS_REC != 0 { 725 finalFlags |= syscall.MS_REC 726 } 727 return p.add(tag, "", dest, "", finalFlags, "") 728 } 729 730 // SetContext sets SELinux mount context, once set it can't be modified 731 func (p *Points) SetContext(context string) error { 732 if p.context == "" { 733 p.context = context 734 return nil 735 } 736 return fmt.Errorf("mount context has already been set") 737 } 738 739 // GetContext returns SELinux mount context 740 func (p *Points) GetContext() string { 741 return p.context 742 }