pkg.re/essentialkaos/ek.v10@v12.41.0+incompatible/fsutil/fs.go (about) 1 //go:build !windows 2 // +build !windows 3 4 // Package fsutil provides methods for working with files on POSIX compatible systems 5 package fsutil 6 7 // ////////////////////////////////////////////////////////////////////////////////// // 8 // // 9 // Copyright (c) 2022 ESSENTIAL KAOS // 10 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 11 // // 12 // ////////////////////////////////////////////////////////////////////////////////// // 13 14 import ( 15 "errors" 16 "fmt" 17 "os" 18 "strings" 19 "syscall" 20 "time" 21 22 PATH "pkg.re/essentialkaos/ek.v12/path" 23 "pkg.re/essentialkaos/ek.v12/system" 24 ) 25 26 // ////////////////////////////////////////////////////////////////////////////////// // 27 28 const ( 29 _IFMT = 0xF000 30 _IFSOCK = 0xC000 31 _IFREG = 0x8000 32 _IFBLK = 0x6000 33 _IFDIR = 0x4000 34 _IFCHR = 0x2000 35 _IRUSR = 0x100 36 _IWUSR = 0x80 37 _IXUSR = 0x40 38 _IRGRP = 0x20 39 _IWGRP = 0x10 40 _IXGRP = 0x8 41 _IROTH = 0x4 42 _IWOTH = 0x2 43 _IXOTH = 0x1 44 ) 45 46 // ////////////////////////////////////////////////////////////////////////////////// // 47 48 // ErrEmptyPath can be returned by different methods if given path is empty and can't be 49 // used 50 var ErrEmptyPath = errors.New("Path is empty") 51 52 // ////////////////////////////////////////////////////////////////////////////////// // 53 54 // CheckPerms checks many props at once 55 // 56 // * F - is file 57 // * D - is directory 58 // * X - is executable 59 // * L - is link 60 // * W - is writable 61 // * R - is readable 62 // * B - is block device 63 // * C - is character device 64 // * S - not empty (only for files) 65 // 66 func CheckPerms(props, path string) bool { 67 if props == "" || path == "" { 68 return false 69 } 70 71 path = PATH.Clean(path) 72 props = strings.ToUpper(props) 73 74 var stat = &syscall.Stat_t{} 75 76 err := syscall.Stat(path, stat) 77 78 if err != nil { 79 return false 80 } 81 82 user, err := getCurrentUser() 83 84 if err != nil { 85 return false 86 } 87 88 for _, k := range props { 89 switch k { 90 91 case 'F': 92 if stat.Mode&_IFMT != _IFREG { 93 return false 94 } 95 96 case 'D': 97 if stat.Mode&_IFMT != _IFDIR { 98 return false 99 } 100 101 case 'B': 102 if stat.Mode&_IFMT != _IFBLK { 103 return false 104 } 105 106 case 'C': 107 if stat.Mode&_IFMT != _IFCHR { 108 return false 109 } 110 111 case 'L': 112 if !IsLink(path) { 113 return false 114 } 115 116 case 'X': 117 if !isExecutableStat(stat, user.UID, getGIDList(user)) { 118 return false 119 } 120 121 case 'W': 122 if !isWritableStat(stat, user.UID, getGIDList(user)) { 123 return false 124 } 125 126 case 'R': 127 if !isReadableStat(stat, user.UID, getGIDList(user)) { 128 return false 129 } 130 131 case 'S': 132 if stat.Size == 0 { 133 return false 134 } 135 } 136 } 137 138 return true 139 } 140 141 // ValidatePerms validates permissions for file or directory 142 func ValidatePerms(props, path string) error { 143 if props == "" || path == "" { 144 return errors.New("Props or path to object is empty") 145 } 146 147 path = PATH.Clean(path) 148 props = strings.ToUpper(props) 149 150 var stat = &syscall.Stat_t{} 151 152 err := syscall.Stat(path, stat) 153 154 if err != nil { 155 switch { 156 case strings.ContainsRune(props, 'F'): 157 return fmt.Errorf("File %s doesn't exist or not accessible", path) 158 case strings.ContainsRune(props, 'D'): 159 return fmt.Errorf("Directory %s doesn't exist or not accessible", path) 160 case strings.ContainsRune(props, 'B'): 161 return fmt.Errorf("Block device %s doesn't exist or not accessible", path) 162 case strings.ContainsRune(props, 'C'): 163 return fmt.Errorf("Character device %s doesn't exist or not accessible", path) 164 case strings.ContainsRune(props, 'L'): 165 return fmt.Errorf("Link %s doesn't exist or not accessible", path) 166 } 167 168 return fmt.Errorf("Object %s doesn't exist or not accessible", path) 169 } 170 171 user, err := getCurrentUser() 172 173 if err != nil { 174 return errors.New("Can't get information about the current user") 175 } 176 177 for _, k := range props { 178 switch k { 179 180 case 'F': 181 if stat.Mode&_IFMT != _IFREG { 182 return fmt.Errorf("%s is not a file", path) 183 } 184 185 case 'D': 186 if stat.Mode&_IFMT != _IFDIR { 187 return fmt.Errorf("%s is not a directory", path) 188 } 189 190 case 'B': 191 if stat.Mode&_IFMT != _IFBLK { 192 return fmt.Errorf("%s is not a block device", path) 193 } 194 195 case 'C': 196 if stat.Mode&_IFMT != _IFCHR { 197 return fmt.Errorf("%s is not a character device", path) 198 } 199 200 case 'L': 201 if !IsLink(path) { 202 return fmt.Errorf("%s is not a link", path) 203 } 204 205 case 'X': 206 if !isExecutableStat(stat, user.UID, getGIDList(user)) { 207 return fmt.Errorf("%s is not executable", path) 208 } 209 210 case 'W': 211 if !isWritableStat(stat, user.UID, getGIDList(user)) { 212 return fmt.Errorf("%s is not writable", path) 213 } 214 215 case 'R': 216 if !isReadableStat(stat, user.UID, getGIDList(user)) { 217 return fmt.Errorf("%s is not readable", path) 218 } 219 220 case 'S': 221 if stat.Size == 0 { 222 return fmt.Errorf("%s is empty", path) 223 } 224 } 225 } 226 227 return nil 228 } 229 230 // ProperPath returns the first proper path from a given slice 231 func ProperPath(props string, paths []string) string { 232 for _, path := range paths { 233 path = PATH.Clean(path) 234 235 if CheckPerms(props, path) { 236 return path 237 } 238 } 239 240 return "" 241 } 242 243 // IsExist returns true if the given object is exist 244 func IsExist(path string) bool { 245 if path == "" { 246 return false 247 } 248 249 path = PATH.Clean(path) 250 251 return syscall.Access(path, syscall.F_OK) == nil 252 } 253 254 // IsRegular returns true if the given object is a regular file 255 func IsRegular(path string) bool { 256 if path == "" { 257 return false 258 } 259 260 path = PATH.Clean(path) 261 mode := getMode(path) 262 263 if mode == 0 { 264 return false 265 } 266 267 return mode&_IFMT == _IFREG 268 } 269 270 // IsSocket returns true if the given object is a socket 271 func IsSocket(path string) bool { 272 if path == "" { 273 return false 274 } 275 276 path = PATH.Clean(path) 277 mode := getMode(path) 278 279 if mode == 0 { 280 return false 281 } 282 283 return mode&_IFMT == _IFSOCK 284 } 285 286 // IsBlockDevice returns true if the given object is a device 287 func IsBlockDevice(path string) bool { 288 if path == "" { 289 return false 290 } 291 292 path = PATH.Clean(path) 293 mode := getMode(path) 294 295 if mode == 0 { 296 return false 297 } 298 299 return mode&_IFMT == _IFBLK 300 } 301 302 // IsCharacterDevice returns true if the given object is a character device 303 func IsCharacterDevice(path string) bool { 304 if path == "" { 305 return false 306 } 307 308 path = PATH.Clean(path) 309 mode := getMode(path) 310 311 if mode == 0 { 312 return false 313 } 314 315 return mode&_IFMT == _IFCHR 316 } 317 318 // IsDir returns true if the given object is a directory 319 func IsDir(path string) bool { 320 if path == "" { 321 return false 322 } 323 324 path = PATH.Clean(path) 325 mode := getMode(path) 326 327 if mode == 0 { 328 return false 329 } 330 331 return mode&_IFMT == _IFDIR 332 } 333 334 // IsLink returns true if the given object is a link 335 func IsLink(path string) bool { 336 if path == "" { 337 return false 338 } 339 340 path = PATH.Clean(path) 341 342 var buf = make([]byte, 1) 343 _, err := syscall.Readlink(path, buf) 344 345 return err == nil 346 } 347 348 // IsReadable returns true if given object is readable by current user 349 func IsReadable(path string) bool { 350 if path == "" { 351 return false 352 } 353 354 path = PATH.Clean(path) 355 356 var stat = &syscall.Stat_t{} 357 358 err := syscall.Stat(path, stat) 359 360 if err != nil { 361 return false 362 } 363 364 user, err := getCurrentUser() 365 366 if err != nil { 367 return false 368 } 369 370 return isReadableStat(stat, user.UID, getGIDList(user)) 371 } 372 373 // IsReadableByUser returns true if given object is readable by some user 374 func IsReadableByUser(path, userName string) bool { 375 if path == "" { 376 return false 377 } 378 379 path = PATH.Clean(path) 380 381 var stat = &syscall.Stat_t{} 382 383 err := syscall.Stat(path, stat) 384 385 if err != nil { 386 return false 387 } 388 389 user, err := system.LookupUser(userName) 390 391 if err != nil { 392 return false 393 } 394 395 return isReadableStat(stat, user.UID, getGIDList(user)) 396 } 397 398 // IsWritable returns true if given object is writable by current user 399 func IsWritable(path string) bool { 400 if path == "" { 401 return false 402 } 403 404 path = PATH.Clean(path) 405 406 var stat = &syscall.Stat_t{} 407 408 err := syscall.Stat(path, stat) 409 410 if err != nil { 411 return false 412 } 413 414 user, err := getCurrentUser() 415 416 if err != nil { 417 return false 418 } 419 420 return isWritableStat(stat, user.UID, getGIDList(user)) 421 } 422 423 // IsWritableByUser returns true if given object is writable by some user 424 func IsWritableByUser(path, userName string) bool { 425 if path == "" { 426 return false 427 } 428 429 path = PATH.Clean(path) 430 431 var stat = &syscall.Stat_t{} 432 433 err := syscall.Stat(path, stat) 434 435 if err != nil { 436 return false 437 } 438 439 user, err := system.LookupUser(userName) 440 441 if err != nil { 442 return false 443 } 444 445 return isWritableStat(stat, user.UID, getGIDList(user)) 446 } 447 448 // IsExecutable returns true if given object is executable by current user 449 func IsExecutable(path string) bool { 450 if path == "" { 451 return false 452 } 453 454 path = PATH.Clean(path) 455 456 var stat = &syscall.Stat_t{} 457 458 err := syscall.Stat(path, stat) 459 460 if err != nil { 461 return false 462 } 463 464 user, err := getCurrentUser() 465 466 if err != nil { 467 return false 468 } 469 470 return isExecutableStat(stat, user.UID, getGIDList(user)) 471 } 472 473 // IsExecutableByUser returns true if given object is executable by some user 474 func IsExecutableByUser(path, userName string) bool { 475 if path == "" { 476 return false 477 } 478 479 path = PATH.Clean(path) 480 481 var stat = &syscall.Stat_t{} 482 483 err := syscall.Stat(path, stat) 484 485 if err != nil { 486 return false 487 } 488 489 user, err := system.LookupUser(userName) 490 491 if err != nil { 492 return false 493 } 494 495 return isExecutableStat(stat, user.UID, getGIDList(user)) 496 } 497 498 // IsEmpty returns true if given file is empty 499 func IsEmpty(path string) bool { 500 if path == "" { 501 return false 502 } 503 504 path = PATH.Clean(path) 505 506 return GetSize(path) == 0 507 } 508 509 // IsNonEmpty returns true if given file is not empty 510 func IsNonEmpty(path string) bool { 511 if path == "" { 512 return false 513 } 514 515 path = PATH.Clean(path) 516 517 return GetSize(path) > 0 518 } 519 520 // IsEmptyDir returns true if given directory es empty 521 func IsEmptyDir(path string) bool { 522 if path == "" { 523 return false 524 } 525 526 path = PATH.Clean(path) 527 528 fd, err := syscall.Open(path, syscall.O_RDONLY, 0) 529 530 if err != nil { 531 return false 532 } 533 534 defer syscall.Close(fd) 535 536 n, err := syscall.ReadDirent(fd, make([]byte, 4096)) 537 538 if isEmptyDirent(n) || err != nil { 539 return true 540 } 541 542 return false 543 } 544 545 // GetOwner returns object owner UID and GID 546 func GetOwner(path string) (int, int, error) { 547 if path == "" { 548 return -1, -1, ErrEmptyPath 549 } 550 551 path = PATH.Clean(path) 552 553 var stat = &syscall.Stat_t{} 554 555 err := syscall.Stat(path, stat) 556 557 if err != nil { 558 return -1, -1, err 559 } 560 561 return int(stat.Uid), int(stat.Gid), nil 562 } 563 564 // GetATime returns time of last access 565 func GetATime(path string) (time.Time, error) { 566 if path == "" { 567 return time.Time{}, ErrEmptyPath 568 } 569 570 path = PATH.Clean(path) 571 572 atime, _, _, err := GetTimes(path) 573 574 return atime, err 575 } 576 577 // GetCTime returns time of creation 578 func GetCTime(path string) (time.Time, error) { 579 if path == "" { 580 return time.Time{}, ErrEmptyPath 581 } 582 583 path = PATH.Clean(path) 584 585 _, _, ctime, err := GetTimes(path) 586 587 return ctime, err 588 } 589 590 // GetMTime returns time of modification 591 func GetMTime(path string) (time.Time, error) { 592 if path == "" { 593 return time.Time{}, ErrEmptyPath 594 } 595 596 path = PATH.Clean(path) 597 598 _, mtime, _, err := GetTimes(path) 599 600 return mtime, err 601 } 602 603 // GetSize returns file size in bytes 604 func GetSize(path string) int64 { 605 if path == "" { 606 return -1 607 } 608 609 path = PATH.Clean(path) 610 611 var stat = &syscall.Stat_t{} 612 613 err := syscall.Stat(path, stat) 614 615 if err != nil { 616 return -1 617 } 618 619 return stat.Size 620 } 621 622 // GetMode returns file mode bits 623 func GetMode(path string) os.FileMode { 624 if path == "" { 625 return 0 626 } 627 628 path = PATH.Clean(path) 629 630 return os.FileMode(getMode(path) & 0777) 631 } 632 633 // ////////////////////////////////////////////////////////////////////////////////// // 634 635 func getMode(path string) uint32 { 636 var stat = &syscall.Stat_t{} 637 638 err := syscall.Stat(path, stat) 639 640 if err != nil { 641 return 0 642 } 643 644 return uint32(stat.Mode) 645 } 646 647 func isReadableStat(stat *syscall.Stat_t, uid int, gids []int) bool { 648 if uid == 0 { 649 return true 650 } 651 652 if stat.Mode&_IROTH == _IROTH { 653 return true 654 } 655 656 if stat.Mode&_IRUSR == _IRUSR && uid == int(stat.Uid) { 657 return true 658 } 659 660 for _, gid := range gids { 661 if stat.Mode&_IRGRP == _IRGRP && gid == int(stat.Gid) { 662 return true 663 } 664 } 665 666 return false 667 } 668 669 func isWritableStat(stat *syscall.Stat_t, uid int, gids []int) bool { 670 if uid == 0 { 671 return true 672 } 673 674 if stat.Mode&_IWOTH == _IWOTH { 675 return true 676 } 677 678 if stat.Mode&_IWUSR == _IWUSR && uid == int(stat.Uid) { 679 return true 680 } 681 682 for _, gid := range gids { 683 if stat.Mode&_IWGRP == _IWGRP && gid == int(stat.Gid) { 684 return true 685 } 686 } 687 688 return false 689 } 690 691 func isExecutableStat(stat *syscall.Stat_t, uid int, gids []int) bool { 692 if uid == 0 { 693 return true 694 } 695 696 if stat.Mode&_IXOTH == _IXOTH { 697 return true 698 } 699 700 if stat.Mode&_IXUSR == _IXUSR && uid == int(stat.Uid) { 701 return true 702 } 703 704 for _, gid := range gids { 705 if stat.Mode&_IXGRP == _IXGRP && gid == int(stat.Gid) { 706 return true 707 } 708 } 709 710 return false 711 } 712 713 func getGIDList(user *system.User) []int { 714 if user == nil { 715 return nil 716 } 717 718 var result []int 719 720 for _, group := range user.Groups { 721 result = append(result, group.GID) 722 } 723 724 return result 725 }