1 /* This file is part of the Amalthea library. 2 * 3 * Copyright (C) 2018-2024 Eugene 'Vindex' Stulin 4 * 5 * Distributed under the Boost Software License 1.0 or (at your option) 6 * the GNU Lesser General Public License 3.0 or later. 7 */ 8 9 module amalthea.fs; 10 11 import core.sys.posix.sys.stat, 12 core.sys.posix.fcntl, 13 core.sys.posix.dirent, 14 core.stdc.errno; 15 import core.sys.linux.sys.xattr : getxattr, setxattr, listxattr, removexattr; 16 import unix_unistd = core.sys.posix.unistd; 17 import unix_stat = core.sys.posix.sys.stat; 18 19 import std.conv : oct = octal; 20 import std.stdio : File; 21 import std.file, std.path; 22 public import std.typecons; 23 24 import std.algorithm, std.array, std.datetime.systime, std.string; 25 import std.regex : regex, matchFirst; 26 import std.algorithm.iteration : splitter; 27 28 public import amalthea.libcore; 29 import amalthea.sys, amalthea.time; 30 import amalthea.dataprocessing : isAmong; 31 32 alias mv = std.file.rename; 33 alias cd = std.file.chdir; 34 alias pwd = getcwd; 35 alias rm = std.file.remove; 36 37 38 immutable size_t eloopLimit = 128; 39 40 41 /******************************************************************************* 42 * Checks if the path is an empty directory. 43 */ 44 bool isEmptyDir(string dirPath) { 45 if (!isDir(dirPath)) { 46 return false; 47 } 48 foreach(FileEntry ent; DirReader(dirPath)) { 49 string name = baseName(ent.path); 50 if (name != "." && name != "..") { 51 return false; 52 } 53 } 54 return true; 55 } 56 57 58 /******************************************************************************* 59 * Implementation of file list reader. 60 * Can be used in foreach as input range, the generator returns FileEntry. 61 */ 62 struct DirReader { 63 this(string dir) { 64 this.directory = dir; 65 errno = 0; 66 this.dirstream = opendir(dir.toStringz); 67 enforce(errno == 0, new FileException( 68 dir, "Failed to open directory: " ~ getInfoAboutError(errno)) 69 ); 70 this.popFront(); 71 } 72 73 ~this() { 74 closedir(dirstream); 75 } 76 77 @property empty() const { 78 return this.currentDirent is null; 79 } 80 81 FileEntry popFront() { 82 assert(!end); 83 auto prevFileEntry = this.currentFileEntry; 84 errno = 0; 85 this.currentDirent = readdir(dirstream); 86 enforce( 87 !errno, 88 new FileException(this.directory, getInfoAboutError(errno)) 89 ); 90 if (this.currentDirent !is null) { 91 string name = (*currentDirent).d_name.ptr.fromStringz.idup; 92 string path = buildPath(this.directory, name); 93 this.currentFileEntry = FileEntry(path); 94 } else { 95 end = true; 96 } 97 return prevFileEntry; 98 } 99 100 FileEntry front() const { 101 assert(this.currentDirent !is null); 102 return this.currentFileEntry; 103 } 104 105 private: 106 107 dirent* currentDirent; 108 DIR* dirstream; 109 string directory; 110 FileEntry currentFileEntry; 111 bool end = false; 112 } 113 114 115 /******************************************************************************* 116 * Reads entries of the directory. 117 * Params: 118 * dir = Directory for listing files. 119 * hidden = Whether to include hidden files. Yes, by default. 120 * recursively = Whether to search in subdirectories. No, by default. 121 * saveAbsolutePath = Whether to store an absolute path or not. If Yes, 122 * a relative path of dir is converted to absolute. 123 * The parameter has no effect if dir is an absolute 124 * path. Has value Yes, by default. 125 * enableFilter = Whether to filter by type. No, by default. 126 * includedTypes = Types for including to final result. 127 * Used in conjunction with the previous parameter. 128 * See_Also: 129 * $(FS_REF getRegularFiles), 130 * $(FS_REF getDirectories), 131 * $(FS_REF getSymlinks). 132 */ 133 FileEntry[] getFiles(string dir, 134 Flag!"hidden" hidden = Yes.hidden, 135 Flag!"recursively" recursively = No.recursively, 136 Flag!"absPath" saveAbsolutePath = Yes.absPath, 137 Flag!"enableFilter" enableFilter = No.enableFilter, 138 FileType[] includedTypes = []) { 139 dir = saveAbsolutePath ? getAbsolutePath(dir) : dir; 140 errno = 0; 141 DIR* dirstream = opendir(dir.toStringz); 142 string errInfo = "Failed to open directory: " ~ getInfoAboutError(errno); 143 enforce(!errno, new FileException(dir, errInfo)); 144 scope(exit) closedir(dirstream); 145 dirent* dirEntry = readdir(dirstream); 146 enforce(!errno, new FileException(dir, getInfoAboutError(errno))); 147 FileEntry[] entries; 148 for (; dirEntry; dirEntry = readdir(dirstream)) { 149 string name = (*dirEntry).d_name.ptr.fromStringz.idup; 150 if (hidden && name.isAmong(".", "..")) continue; 151 if (!hidden && name.startsWith('.')) continue; 152 string path = buildPath(dir, name); 153 FileEntry entry = FileEntry(path); 154 if (recursively && entry.type == FileType.directory) { 155 entries ~= getFiles( 156 path, 157 hidden, 158 recursively, 159 saveAbsolutePath, 160 enableFilter, 161 includedTypes 162 ); 163 } 164 165 if (!enableFilter || entry.type.isAmong(includedTypes)) { 166 entries ~= entry; 167 } 168 } 169 enforce(!errno, new FileException(dir, errno)); 170 return entries; 171 } 172 173 174 /******************************************************************************* 175 * Gets regular files from the directory. 176 * Params: 177 * dir = Directory for listing regular files. 178 * hidden = Whether to include hidden files. Yes, by default. 179 * recursively = Whether to search in subdirectories. No, by default. 180 * saveAbsolutePath = Whether to write the full path. Yes, by default. 181 */ 182 FileEntry[] getRegularFiles(string dir, 183 Flag!"hidden" hidden = Yes.hidden, 184 Flag!"recursively" recursively = No.recursively, 185 Flag!"absPath" saveAbsolutePath = Yes.absPath) { 186 FileType[] includedTypes = [FileType.regularFile]; 187 return getFiles(dir, hidden, recursively, saveAbsolutePath, 188 Yes.enableFilter, includedTypes); 189 } 190 191 192 /******************************************************************************* 193 * Gets directories from the directory. 194 * Params: 195 * dir = Directory for listing directories. 196 * hidden = Whether to include hidden files. Yes, by default. 197 * recursively = Whether to search in subdirectories. No, by default. 198 * saveAbsolutePath = Whether to write the full path. Yes, by default. 199 */ 200 FileEntry[] getDirectories(string dir, 201 Flag!"hidden" hidden = Yes.hidden, 202 Flag!"recursively" recursively = No.recursively, 203 Flag!"absPath" saveAbsolutePath = Yes.absPath) { 204 FileType[] includedTypes = [FileType.directory]; 205 return getFiles(dir, hidden, recursively, saveAbsolutePath, 206 Yes.enableFilter, includedTypes); 207 } 208 209 210 /******************************************************************************* 211 * Gets symlinks from the directory. 212 * Params: 213 * dir = Directory for listing symlinks. 214 * hidden = Whether to include hidden files. Yes, by default. 215 * recursively = Whether to search in subdirectories. No, by default. 216 * saveAbsolutePath = Whether to write the full path. Yes, by default. 217 */ 218 FileEntry[] getSymlinks(string dir, 219 Flag!"hidden" hidden = Yes.hidden, 220 Flag!"recursively" recursively = No.recursively, 221 Flag!"absPath" saveAbsolutePath = Yes.absPath) { 222 FileType[] includedTypes = [FileType.symlink]; 223 return getFiles(dir, hidden, recursively, saveAbsolutePath, 224 Yes.enableFilter, includedTypes); 225 } 226 227 228 /****************************************************************************** 229 * Special type for files as entries in directories. 230 * Analogue of the 'dirent' structure type. 231 */ 232 struct FileEntry { 233 /// Path to file. 234 string path; 235 /// Inode serial number. 236 ulong fileno; 237 /// UNIX file type. See: enum FileType. 238 FileType type; 239 240 /// Creates an instance of a class from an existing file by its path. 241 this(string filepath) { 242 auto st = FileStat(filepath); 243 this.path = filepath; 244 this.fileno = st.ino; 245 this.type = st.type; 246 } 247 248 /// Returns information about the properties of the file. 249 const FileStat getFileStat() { 250 return FileStat(this.path); 251 } 252 253 /*************************************************************************** 254 * Checks if the file is a symbolic link to a directory. 255 * The check is recursive (if the link points to a link). 256 */ 257 bool isLinkToDir() { 258 if (type != FileType.symlink) { 259 return false; 260 } 261 string p; 262 try { 263 p = readLinkRecurse(this.path); 264 } catch (FileException e) { 265 return false; 266 } 267 return FileStat(p).type == FileType.directory; 268 } 269 } 270 271 272 /****************************************************************************** 273 * Returns read symlink path. If the symlink points to other symlink, 274 * the function returns path of the last symlink of the link chain. 275 */ 276 string readLinkRecurse(string link) { 277 size_t counter = 1; 278 auto path = link.readLink; 279 if (!path.exists) { 280 return path; 281 } 282 while(path.isSymlink) { 283 path = path.readLink; 284 counter++; 285 if (counter > eloopLimit) { 286 errno = ELOOP; 287 throw new FileException(path, errno); 288 } 289 if (!path.exists) { 290 // broken link 291 return path; 292 } 293 } 294 return path; 295 } 296 297 298 299 /******************************************************************************* 300 * Checks if a file is a broken symbolic link is broken 301 * (whether indicates a non-existent location). 302 * Params: 303 * link = Path to link. 304 * recurse = Whether to check all nested symbolic links in a chain. 305 */ 306 bool isBrokenSymlink(string link, Flag!"recurse" recurse = No.recurse) 307 do { 308 if (!link.isSymlink) { 309 return false; 310 } 311 if (recurse) { 312 string path = link.readLink; 313 size_t counter = 1; 314 while (path.exists) { 315 if (!path.isSymlink) { 316 return false; 317 } 318 path = path.readLink; 319 counter++; 320 if (counter > eloopLimit) { 321 errno = ELOOP; 322 throw new FileException(path, errno); 323 } 324 } 325 return true; 326 } 327 return !link.readLink.exists; 328 } 329 330 331 /// Checks if a path is a pipe file (FIFO). 332 bool isPipe(in string path) { 333 return FileStat(path).type == FileType.pipe; 334 } 335 336 337 /// Checks if a path is a socket. 338 bool isSocket(in string path) { 339 return FileStat(path).type == FileType.socket; 340 } 341 342 343 /// Checks if a path is a symbolic link. 344 bool isSymlink(in string path) { 345 return FileStat(path).type == FileType.symlink; 346 } 347 348 349 /// Checks if a path is a regular file. 350 bool isRegularFile(in string path) { 351 return FileStat(path).type == FileType.regularFile; 352 } 353 /// Checks if a FileEntry object related to a regular file. 354 bool isRegularFile(const FileEntry entry) { 355 return entry.type == FileType.directory; 356 } 357 bool isRegularFile(ref const FileEntry entry) { 358 return entry.type == FileType.directory; 359 } 360 361 362 /// Checks if a path is a block device. 363 bool isBlockDevice(in string path) { 364 return FileStat(path).type == FileType.blockDevice; 365 } 366 367 368 /// Checks if a path is a directory. 369 bool isDir(in string path) { 370 return FileStat(path).type == FileType.directory; 371 } 372 /// Checks if a FileEntry object related to a directory. 373 bool isDir(const FileEntry entry) { 374 return entry.type == FileType.directory; 375 } 376 bool isDir(ref const FileEntry entry) { 377 return entry.type == FileType.directory; 378 } 379 380 381 382 /*************************************************************************** 383 * Checks if the file is a symbolic link to a directory. 384 * The check is recursive (if the link points to a link). 385 * Params: 386 * path = path fo file 387 * Returns: whether a symbolic link is a link to a directory. 388 */ 389 bool isSymlinkToDir(in string path) { 390 return FileEntry(path).isLinkToDir(); 391 } 392 393 394 /// Checks if a path is a character device. 395 bool isCharDevice(in string path) { 396 return FileStat(path).type == FileType.charDevice; 397 } 398 399 400 /******************************************************************************* 401 * Special set of functions for work with ini-like .desktop files. 402 */ 403 class DesktopEntry { 404 405 /*************************************************************************** 406 * This function adds new field to ini-like .desktop file. 407 * Params: 408 * filepath = path to .desktop file 409 * field = field name 410 * value = value for new field 411 */ 412 static void addField(string filepath, string field, string value) { 413 string content = std.file.readText(filepath); 414 auto newLine = field ~ "=" ~ value ~ "\n"; 415 if (content.endsWith("\n")) { 416 content ~= newLine; 417 } else { 418 content ~= "\n" ~ newLine; 419 } 420 std.file.write(filepath, content); 421 } 422 423 /*************************************************************************** 424 * This function returns value of specified field from .desktop file. 425 * Params: 426 * filepath = path to .desktop file 427 * field = field name 428 */ 429 static string getField(string filepath, string field) { 430 field = field.replace(`[`, `\[`).replace(`]`, `\]`); 431 string content = std.file.readText(filepath); 432 auto reg = regex(field ~ `=(.*)`); 433 auto res = matchFirst(content, reg); 434 return res[1].idup; 435 } 436 437 /*************************************************************************** 438 * This function sets new value for existent field from .desktop file. 439 * Params: 440 * filepath = path to .desktop file 441 * field = field name 442 * value = new value of the field 443 */ 444 static void setField(string filepath, string field, string value) { 445 string oldValue = DesktopEntry.getField(filepath, field); 446 string content = std.file.readText(filepath); 447 content = content.replace(field~"="~oldValue, field~"="~value); 448 std.file.write(filepath, content); 449 } 450 451 /*************************************************************************** 452 * The function returns title from .desktop file (field "Name"). 453 */ 454 static string getTitle(string pathToDesktopFile) { 455 string desktopEntry = readText(pathToDesktopFile); 456 auto indexOfNameField = desktopEntry.indexOf("Name="); 457 auto indexOfTypeField = desktopEntry.indexOf("\nType="); 458 return desktopEntry[indexOfNameField + 4 .. indexOfTypeField].idup; 459 } 460 461 /*************************************************************************** 462 * Creates a .desktop file with the link to the network resource. 463 * Params: 464 * networkPath = path to the network resource 465 * destinationDir = directory for saving 466 * name = final desktop-file name (if empty, the name is taken from 467 * the title of the HTML page or from network address) 468 * Returns: path to created desktop file 469 */ 470 static string createNetworkLink(string networkPath, 471 string destinationDir, 472 string name="") { 473 import amalthea.net : isLinkToHTML, getHTMLPageTitle; 474 if (name.empty) { 475 bool HTML = isLinkToHTML(networkPath); 476 name = HTML ? getHTMLPageTitle(networkPath) 477 : baseName(networkPath); 478 if (name.empty) { 479 name = networkPath; 480 } 481 } 482 string desktopEntry = "[Desktop Entry]\n" 483 ~ "Encoding=UTF-8\n" 484 ~ "Name=" ~ name ~ "\n" 485 ~ "Type=Link\n" 486 ~ "URL=" ~ networkPath ~ "\n" 487 ~ "Comment=" ~ "\n" 488 ~ "Icon=text-html"; 489 string finalFileName = name.replace("/", "|"); 490 if (!endsWith(name, ".desktop")) { 491 finalFileName ~= ".desktop"; 492 } 493 string filePath = chainPath(destinationDir, finalFileName).array; 494 std.file.write(filePath, desktopEntry); 495 return filePath; 496 } 497 498 /*************************************************************************** 499 * Returns link to network resource from .desktop file (field "URL"). 500 */ 501 static string getURL(string pathToDesktopFile) { 502 return DesktopEntry.getField(pathToDesktopFile, "URL"); 503 } 504 } 505 506 507 /******************************************************************************* 508 * Returns absolute path for any raw path. Trailing slashes are removed. 509 */ 510 string getAbsolutePath(string path) { 511 import std.path : buildNormalizedPath, absolutePath; 512 return buildNormalizedPath(absolutePath(path)); 513 } 514 unittest { 515 assert(getAbsolutePath("/usr/bin") == "/usr/bin"); 516 assert(getAbsolutePath("/usr/bin/") == "/usr/bin"); 517 } 518 519 520 /******************************************************************************* 521 * Function for copying files and directories. 522 * Params: 523 * from = source file 524 * to = destination 525 * followLinks = whether to follow symbolic links 526 * passErrors = if No, the 'cp' function can throw exceptions 527 * else errors are ignored 528 * printErrors = print information about errors or not 529 * (the parameter works if passErrors is Yes) 530 */ 531 void cp(string from, 532 string to, 533 Flag!"followLinks" followLinks = No.followLinks, 534 Flag!"passErrors" passErrors = No.passErrors, 535 Flag!"printErrors" printErrors = Yes.printErrors) { 536 537 void copyOneFile(string src, string dest) { 538 try { 539 if (dest.exists) { 540 rm(dest); 541 } 542 alias isBroken = isBrokenSymlink; 543 if (!followLinks && src.isSymlink || isBroken(src, Yes.recurse)) { 544 std.file.symlink(src.readLink, dest); 545 } else if (src.isPipe) { 546 auto pipeStat = FileStat(src); 547 amalthea.sys.createPipe(dest, pipeStat.mode); 548 } else { 549 std.file.copy(src, dest); 550 } 551 } catch (Exception e) { 552 if (!passErrors) { 553 throw e; 554 } 555 if (printErrors) { 556 stderr.writeln(e.msg); 557 } 558 } 559 } 560 561 void processSymlink(FileEntry entry, string dest) { 562 auto newPath = buildPath(dest, entry.path); 563 if (followLinks && entry.isLinkToDir) { 564 std.file.mkdirRecurse(newPath); 565 auto distantDir = entry.path.readLinkRecurse; 566 cp(distantDir, newPath, followLinks, passErrors, printErrors); 567 } else { 568 mkdirRecurse(dirName(newPath)); // create dir over file 569 copyOneFile(entry.path, newPath); 570 } 571 } 572 573 void copyDirToDir(string src, string dest) { 574 to = getAbsolutePath(dest); 575 auto startPath = pwd; 576 cd(src); 577 scope(exit) cd(startPath); 578 FileEntry[] entries; 579 entries = getFiles(".", Yes.hidden, Yes.recursively, No.absPath); 580 foreach(entry; entries) { 581 if (entry.type == FileType.directory) { 582 std.file.mkdirRecurse(buildPath(to, entry.path)); 583 } else if (entry.type == FileType.symlink) { 584 processSymlink(entry, to); 585 } else { // other files 586 auto newPath = buildPath(to, entry.path); 587 mkdirRecurse(dirName(newPath)); // create dir over file 588 copyOneFile(entry.path, newPath); 589 } 590 } 591 } 592 593 enforce(exists(from), new FileException(from, "Not found.")); 594 if (from.isDir && to.exists && !to.isDir) { 595 throw new std.file.FileException(to, "Is not a directory."); 596 } 597 598 if (from.isDir && !to.exists) { 599 std.file.mkdirRecurse(to); 600 } 601 602 if (from.isDir && to.isDir) { // dir to dir 603 copyDirToDir(from, to); 604 } else if (!from.isDir && to.exists && to.isDir) { // file to dir 605 copyOneFile(from, buildPath(to, baseName(from))); 606 } else { //file to file 607 copyOneFile(from, to); 608 } 609 } 610 611 612 /// A low-level structure to filling with the 'statx' system call. 613 extern(C) 614 struct statx_t { 615 // 0x00 616 uint stx_mask; 617 uint stx_blksize; 618 ulong stx_attributes; 619 // 0x10 620 uint stx_nlink; 621 uint stx_uid; 622 uint stx_gid; 623 ushort stx_mode; 624 ushort[1] __spare0; 625 // 0x20 626 ulong stx_ino; 627 ulong stx_size; 628 ulong stx_blocks; 629 ulong stx_attributes_mask; 630 // 0x40 631 timestamp_t stx_atime; 632 timestamp_t stx_btime; 633 timestamp_t stx_ctime; 634 timestamp_t stx_mtime; 635 // 0x80 636 uint stx_rdev_major; 637 uint stx_rdev_minor; 638 uint stx_dev_major; 639 uint stx_dev_minor; 640 // 0x90 641 ulong stx_mnt_id; 642 ulong __spare2; 643 // 0xA0 644 ulong[12] __spare3; // Spare space for future expansion 645 } 646 647 648 /******************************************************************************* 649 * This function returns information about a file, 650 * storing it in the buffer pointed to by statxbuf. 651 * System call of Linux 4.11+, see more: `man statx` 652 * This function is used in the high-level FileStat structure type. 653 */ 654 extern(C) 655 int statx(int dfd, const char* filename, int flags, uint mask, statx_t* stx); 656 657 /// See: `man statx` 658 enum STATX_TYPE = 0x00000001U; 659 enum STATX_MODE = 0x00000002U; 660 enum STATX_NLINK = 0x00000004U; 661 enum STATX_UID = 0x00000008U; 662 enum STATX_GID = 0x00000010U; 663 enum STATX_ATIME = 0x00000020U; 664 enum STATX_MTIME = 0x00000040U; 665 enum STATX_CTIME = 0x00000080U; 666 enum STATX_INO = 0x00000100U; 667 enum STATX_SIZE = 0x00000200U; 668 enum STATX_BLOCKS = 0x00000400U; 669 enum STATX_BASIC_STATS = 0x000007ffU; 670 enum STATX_BTIME = 0x00000800U; 671 enum STATX_MNT_ID = 0x00001000U; 672 673 /// Mask for stat field by default. 674 enum STATX_DEFAULT = STATX_BASIC_STATS | STATX_BTIME; 675 676 677 /******************************************************************************* 678 * Returns file information as statx_t. 679 * Wrapper for low level statx call. 680 */ 681 statx_t getStatX(string filepath) { 682 int atflag = AT_SYMLINK_NOFOLLOW; 683 statx_t stx; 684 char* p = cast(char*)toStringz(filepath); 685 auto ret = statx(AT_FDCWD, p, atflag, STATX_DEFAULT, &stx); 686 if (ret < 0) { 687 throw new FileException(filepath, getInfoAboutError(errno)); 688 } 689 return stx; 690 } 691 692 693 /// High-level function for getting stat_t from file path. 694 stat_t getStat(string filepath) { 695 stat_t st; 696 int ret = lstat(filepath.toStringz, &st); 697 enforce(ret != -1, new FileException(filepath, errno)); 698 return st; 699 } 700 701 702 /******************************************************************************* 703 * A structure for storing and retrieving file information on Linux. 704 */ 705 struct FileStat { 706 /// Mask of bits indicating filled fields. 707 uint mask; 708 /// Block size for filesystem I/O. 709 uint blksize; 710 /// Extra file attribute indicators. 711 ulong attributes; 712 /// Number of hard links. 713 uint nlink; 714 /// User ID of owner. 715 uint uid; 716 /// Group ID of owner. 717 uint gid; 718 /// File type and mode. 719 ushort mode; 720 /// Inode number. 721 ulong ino; 722 /// Total size in bytes. 723 ulong size; 724 /// Number of 512B blocks allocated. 725 ulong blocks; 726 /// Mask to show what's supported in stx_attributes. 727 ulong attributes_mask; 728 /// Time of last access. 729 timestamp_t atime; 730 /// Time of creation. 731 timestamp_t btime; 732 /// Time of last modification. 733 timestamp_t mtime; 734 /// Time of last status change. 735 timestamp_t ctime; 736 737 /// Device ID (if this file is a device). 738 ulong rdev; 739 /// Device major id (if this file is a device). 740 uint rdev_major; 741 /// Device minor id (if this file is a device). 742 uint rdev_minor; 743 744 /// ID of device containing file. 745 ulong dev; 746 /// Major ID of the filesystem where the file resides. 747 uint dev_major; 748 /// Minor ID of the filesystem where the file resides. 749 uint dev_minor; 750 751 /// File path. 752 string filepath; 753 754 /// Stored mask for required structure fields. 755 uint fieldMask; 756 757 /*************************************************************************** 758 * The constructor creates object with file information by file path. 759 * Params: 760 * filepath = The path to the file being examined. 761 * fieldMask = Used to tell the kernel which fields 762 * the caller is interested in. 763 */ 764 this(string filepath, uint fieldMask = STATX_DEFAULT) { 765 this.filepath = filepath; 766 this.fieldMask = fieldMask; 767 update(); 768 } 769 770 /*************************************************************************** 771 * This method re-reads and updates file information. 772 */ 773 void update() { 774 enforce( 775 std.file.exists(filepath), 776 new FileException(filepath, "No such file or directory") 777 ); 778 statx_t stx = getStatX(filepath); 779 this.mask = stx.stx_mask; 780 this.blksize = stx.stx_blksize; 781 this.attributes = stx.stx_attributes; 782 this.nlink = stx.stx_nlink; 783 this.uid = stx.stx_uid; 784 this.gid = stx.stx_gid; 785 this.mode = stx.stx_mode; 786 this.ino = stx.stx_ino; 787 this.size = stx.stx_size; 788 this.blocks = stx.stx_blocks; 789 this.attributes_mask = stx.stx_attributes_mask; 790 this.atime = stx.stx_atime; 791 this.btime = stx.stx_btime; 792 this.mtime = stx.stx_mtime; 793 this.ctime = stx.stx_ctime; 794 this.rdev_major = stx.stx_rdev_major; 795 this.rdev_minor = stx.stx_rdev_minor; 796 this.rdev = (cast(ulong)rdev_major << 8) | rdev_minor; 797 this.dev_major = stx.stx_dev_major; 798 this.dev_minor = stx.stx_dev_minor; 799 this.dev = (cast(ulong)dev_major << 8) | dev_minor; 800 } 801 802 /*************************************************************************** 803 * Gets user name of owner. 804 */ 805 @property string user() { 806 return amalthea.sys.getNameByUID(uid); 807 } 808 809 /*************************************************************************** 810 * Gets group name of owner. 811 */ 812 @property string group() { 813 return amalthea.sys.getNameByGID(gid); 814 } 815 816 /*************************************************************************** 817 * Returns file type. 818 */ 819 @property const FileType type() { 820 return getFileTypeFromFileMode(this.mode); 821 } 822 } 823 824 825 /******************************************************************************* 826 * Gets FileType by stat field 'mode'. 827 */ 828 FileType getFileTypeFromFileMode(uint mode) nothrow { 829 return getFileTypeFromFileMask(mode & S_IFMT); 830 } 831 832 833 /******************************************************************************* 834 * Gets FileType by the file mask obtained from stat field 'mode'. 835 */ 836 FileType getFileTypeFromFileMask(uint mask) nothrow { 837 switch(mask) { 838 case S_IFSOCK: return FileType.socket; 839 case S_IFLNK: return FileType.symlink; 840 case S_IFREG: return FileType.regularFile; 841 case S_IFBLK: return FileType.blockDevice; 842 case S_IFDIR: return FileType.directory; 843 case S_IFCHR: return FileType.charDevice; 844 case S_IFIFO: return FileType.pipe; 845 default: return FileType.unknown; 846 } 847 } 848 849 /******************************************************************************* 850 * Enumeration of UNIX file types. 851 */ 852 enum FileType { 853 socket = 's', 854 symlink = 'l', 855 regularFile = '-', 856 blockDevice = 'b', 857 directory = 'd', 858 charDevice = 'c', 859 pipe = 'p', 860 unknown = 'z' 861 } 862 863 864 /******************************************************************************* 865 * The function returns file permissions in a string form (like -rwxrwxrwx) 866 */ 867 string makeUnixFileModeLine(in uint mode) { 868 char[10] permissions; 869 permissions[0] = cast(char)getFileTypeFromFileMode(mode); 870 foreach(shift, rwxMask; [0: S_IRWXU, 3: S_IRWXG, 6: S_IRWXO]) { 871 const rwxMode = (mode & rwxMask) >> (6-shift); 872 permissions[shift+1] = (rwxMode & 4) ? 'r' : '-'; 873 permissions[shift+2] = (rwxMode & 2) ? 'w' : '-'; 874 permissions[shift+3] = (rwxMode & 1) ? 'x' : '-'; 875 } 876 if (mode & S_ISUID) { 877 permissions[3] = 's'; 878 } 879 if (mode & S_ISGID) { 880 permissions[6] = 's'; 881 } 882 if (mode & S_ISVTX) { 883 permissions[9] = 't'; 884 } 885 return permissions.idup; 886 } 887 /// ditto 888 string makeUnixFileModeLine(const FileStat st) { 889 return makeUnixFileModeLine(st.mode); 890 } 891 string makeUnixFileModeLine(ref const FileStat st) { 892 return makeUnixFileModeLine(st.mode); 893 } 894 895 896 /******************************************************************************* 897 * Creates a new hard link (make a new name for a file, see `man 2 link`). 898 * Params: 899 * original = Original file path. 900 * link = New link location path. 901 */ 902 void hardlink(string original, string link) { 903 int ret = unix_unistd.link(original.toStringz, link.toStringz); 904 if (-1 == ret) { 905 string info = format!"Link from %s to %s"(original, link); 906 throw new FileException(info); 907 } 908 } 909 910 private extern(C) { 911 int faccessat( 912 int dirfd, const char* pathname, int mode, int flags 913 ) nothrow @nogc; 914 enum AT_FDCWD = -100; 915 enum AT_SYMLINK_NOFOLLOW = 0x100; 916 enum F_OK = 0; 917 } 918 919 /// Checks if the given file exists. 920 bool exists(in string filepath) nothrow { 921 return F_OK == faccessat( 922 AT_FDCWD, filepath.toStringz, 0, AT_SYMLINK_NOFOLLOW 923 ); 924 } 925 926 927 /******************************************************************************* 928 * Function set for working with file extended attributes. 929 * See_Also: `man xattr` 930 */ 931 class xattr { 932 /** 933 * Gets value of the required extended attribute. 934 * Params: 935 * path = Path to explored file. 936 * If it's a symlink, the target will be used. 937 * key = Attribute name. 938 * Returns: read value. 939 */ 940 static string read(string path, string key) { 941 auto pathPtr = path.toStringz; 942 auto keyPtr = key.toStringz; 943 auto valueLength = getxattr(pathPtr, keyPtr, null, 0); 944 enforce(valueLength != -1, new FileException("getxattr")); 945 char[] buffer = new char[valueLength]; 946 valueLength = getxattr(pathPtr, keyPtr, buffer.ptr, valueLength); 947 enforce(valueLength != -1, new FileException("getxattr")); 948 return buffer.to!string; 949 } 950 951 /** 952 * Sets new value of the required extended attribute. 953 * Params: 954 * path = Path to explored file. 955 * If it's a symlink, the target will be used. 956 * key = Attribute name. 957 * If the attribute doesn't exist, it will be created. 958 * value = New value of the attribute. 959 */ 960 static void set(string path, string key, string value) { 961 auto pathPtr = path.toStringz; 962 auto keyPtr = key.toStringz; 963 auto valuePtr = cast(void*)(value.toStringz); 964 int ret = setxattr(pathPtr, keyPtr, valuePtr, value.length, 0); 965 enforce(ret != -1, new FileException("setxattr")); 966 } 967 968 /** 969 * Removes the requested attribute. 970 * Params: 971 * path = Path to file. 972 * If it's a symlink, the target will be used. 973 * key = Attribute name. 974 */ 975 static void remove(string path, string key) { 976 int ret = removexattr(path.toStringz, key.toStringz); 977 enforce(ret != -1, new FileException("removexattr")); 978 } 979 980 /** 981 * Reads list of all attributes. 982 * Params: 983 * path = Path to explored file. 984 * If it's a symlink, the target will be used. 985 * Returns: extended attributes of the specified file. 986 */ 987 static string[] getAttrs(string path) { 988 auto pathPtr = path.toStringz; 989 auto bufferLength = listxattr(pathPtr, null, 0); 990 enforce(bufferLength != -1, new FileException("listxattr")); 991 ubyte[] buffer = new ubyte[bufferLength]; 992 bufferLength = listxattr(pathPtr, cast(char*)buffer.ptr, bufferLength); 993 enforce(bufferLength != -1, new FileException("listxattr")); 994 if (bufferLength == 0) { 995 return []; 996 } 997 buffer = buffer[0 .. $-1]; // without last zero 998 string[] list; 999 foreach(ubyte[] elem; buffer.splitter(0)) { 1000 list ~= to!string(cast(char[])elem); 1001 } 1002 return list; 1003 } 1004 1005 /** 1006 * Creates associative array with attribute names and attribute values. 1007 * Params: 1008 * path = Path to explored file. 1009 * If it's a symlink, the target will be used. 1010 * Returns: all extended attributes and their values of the specified file. 1011 */ 1012 static string[string] getAttrsAndValues(string path) { 1013 string[] names = xattr.getAttrs(path); 1014 string[string] result; 1015 foreach(attr; names) { 1016 result[attr] = xattr.read(path, attr); 1017 } 1018 return result; 1019 } 1020 } 1021 1022 1023 /******************************************************************************* 1024 * Truncate a file to a specified length. 1025 * Params: 1026 * filepath = File to be resized. 1027 * size = New length in bytes. 1028 */ 1029 void truncate(string filepath, size_t size) { 1030 int ret = unix_unistd.truncate(filepath.toStringz, size); 1031 enforce(ret != -1, new FileException(filepath, errno)); 1032 } 1033 1034 1035 /****************************************************************************** 1036 * Create a directory only if it doesn't exist. 1037 */ 1038 void safeMkdir(string path) { 1039 errno = 0; 1040 int ret = unix_stat.mkdir(path.toStringz, oct!777); 1041 if (ret == -1 && errno == EEXIST) { 1042 return; 1043 } 1044 throw new FileException("safeMkdir"); 1045 } 1046 1047 1048 private extern(C) 1049 int posix_fadvise(int fd, off_t offset, off_t len, int advice) nothrow @nogc; 1050 1051 1052 /// Access patterns for file data. 1053 enum FileAdvice { 1054 normal = 0, /// No further special treatment. 1055 random = 1, /// Expect random page references. 1056 sequential = 2, /// Expect sequential page references. 1057 willNeed = 3, /// Will need these pages. 1058 dontNeed = 4, /// Don't need these pages. 1059 noReuse = 5 /// Data will be accessed once. 1060 } 1061 1062 1063 /******************************************************************************* 1064 * Predeclare an access pattern for file data. 1065 * Params: 1066 * f = File object. 1067 * advice = Access patern. 1068 * 1069 * See_Also: `man 2 posix_fadvise` 1070 */ 1071 void applyFileAdvise(File f, FileAdvice advice) { 1072 int ret = posix_fadvise(f.fileno, 0, 0, cast(int)advice); 1073 enforce(ret == 0, new FileException("applyFileAdvise")); 1074 }