From 0d4b39d37b966eccc27ff383f6453797f576859d Mon Sep 17 00:00:00 2001 From: Corinna Vinschen Date: Tue, 25 Dec 2018 23:39:11 +0100 Subject: [PATCH] Cygwin: Add lsattr and chattr tools Signed-off-by: Corinna Vinschen --- winsup/utils/Makefile.in | 2 +- winsup/utils/chattr.c | 362 +++++++++++++++++++++++++++++++++++++++ winsup/utils/lsattr.c | 289 +++++++++++++++++++++++++++++++ 3 files changed, 652 insertions(+), 1 deletion(-) create mode 100644 winsup/utils/chattr.c create mode 100644 winsup/utils/lsattr.c diff --git a/winsup/utils/Makefile.in b/winsup/utils/Makefile.in index be525d07f..b64f457e7 100644 --- a/winsup/utils/Makefile.in +++ b/winsup/utils/Makefile.in @@ -54,7 +54,7 @@ MINGW_CXX := @MINGW_CXX@ # List all binaries to be linked in Cygwin mode. Each binary on this list # must have a corresponding .o of the same name. -CYGWIN_BINS := ${addsuffix .exe,cygpath gencat getconf getfacl ldd locale kill minidumper mkgroup \ +CYGWIN_BINS := ${addsuffix .exe,chattr cygpath gencat getconf getfacl ldd locale lsattr kill minidumper mkgroup \ mkpasswd mount passwd pldd ps regtool setfacl setmetamode ssp tzset umount} # List all binaries to be linked in MinGW mode. Each binary on this list diff --git a/winsup/utils/chattr.c b/winsup/utils/chattr.c new file mode 100644 index 000000000..c21d4b7a5 --- /dev/null +++ b/winsup/utils/chattr.c @@ -0,0 +1,362 @@ +/* chattr.c + + Written by Corinna Vinschen + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int Ropt, Vopt, fopt; +uint64_t add, del, set; + +struct option longopts[] = { + { "recursive", no_argument, NULL, 'R' }, + { "verbose", no_argument, NULL, 'V' }, + { "force", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { NULL, no_argument, NULL, 0} +}; + +const char *opts = "+RVfhv"; + +struct +{ + uint64_t flagval; + char chr; + const char *str; +} supp_flag[] = { + { FS_READONLY_FL, 'r', "Readonly" }, + { FS_HIDDEN_FL, 'h', "Hidden" }, + { FS_SYSTEM_FL, 's', "System" }, + { FS_ARCHIVE_FL, 'a', "Archive" }, + { FS_TEMP_FL, 't', "Temporary" }, + { FS_SPARSE_FL, 'S', "Sparse" }, + { FS_REPARSE_FL, 'r', NULL }, + { FS_COMPRESSED_FL, 'c', "Compressed" }, + { FS_OFFLINE_FL, 'o', NULL }, + { FS_NOTINDEXED_FL, 'n', "Notindexed" }, + { FS_ENCRYPT_FL, 'e', "Encrypted" }, + { FS_CASESENS_FL, 'C', "Casesensitive" }, + { 0, '\0', NULL }, +}; +const char *supp_list = "rhsatSrconeC"; + +void +print_flags (uint64_t flags) +{ + int i; + + for (i = 0; supp_flag[i].flagval; ++i) + fputc ((flags & supp_flag[i].flagval) ? supp_flag[i].chr : '-', stdout); +} + +int +get_flags (const char *opt) +{ + const char *p = opt, *sl; + uint64_t *mode; + ptrdiff_t idx; + + switch (*p) + { + case '+': + mode = &add; + break; + case '-': + mode = &del; + break; + case '=': + mode = &set; + break; + default: + return 1; + } + while (*++p) + { + sl = strchr (supp_list, *p); + if (!sl) + return 1; + idx = sl - supp_list; + if (!supp_flag[idx].str) + return 1; + *mode |= supp_flag[idx].flagval; + } + return 0; +} + +int +sanity_check () +{ + int ret = -1; + if (!set && !add && !del) + fprintf (stderr, "%s: Must use at least one of =, + or -\n", + program_invocation_short_name); + else if (set && (add | del)) + fprintf (stderr, "%s: = is incompatible with + and -\n", + program_invocation_short_name); + else if ((add & del) != 0) + fprintf (stderr, "%s: Can't both set and unset same flag.\n", + program_invocation_short_name); + else + ret = 0; + return ret; +} + +int +chattr (const char *path) +{ + int fd; + uint64_t flags, newflags; + + fd = open (path, O_RDONLY); + if (fd < 0) + { + fprintf (stderr, "%s: %s while trying to open %s\n", + program_invocation_short_name, strerror (errno), path); + return 1; + } + if (ioctl (fd, FS_IOC_GETFLAGS, &flags)) + { + close (fd); + fprintf (stderr, "%s: %s while trying to fetch flags from %s\n", + program_invocation_short_name, strerror (errno), path); + return 1; + } + if (set) + newflags = set; + else + { + newflags = flags; + newflags |= add; + newflags &= ~del; + } + if (newflags != flags) + { + if (Vopt) + { + printf ("Flags of %s set as ", path); + print_flags (newflags); + fputc ('\n', stdout); + } + if (ioctl (fd, FS_IOC_SETFLAGS, &newflags)) + { + close (fd); + fprintf (stderr, "%s: %s while trying to set flags on %s\n", + program_invocation_short_name, strerror (errno), path); + return 1; + } + } + close (fd); + return 0; +} + +int +chattr_dir (const char *path) +{ + DIR *dir; + struct dirent *de; + char *subpath = (char *) malloc (strlen (path) + 1 + NAME_MAX + 1); + char *comp; + + dir = opendir (path); + if (!dir) + { + free (subpath); + return 1; + } + comp = stpcpy (subpath, path); + if (comp[-1] != '/') + *comp++ = '/'; + while ((de = readdir (dir))) + { + struct stat st; + + if (strcmp (de->d_name, ".") == 0 || strcmp (de->d_name, "..") == 0) + continue; + + stpcpy (comp, de->d_name); + if (lstat (subpath, &st) != 0) + fprintf (stderr, "%s: %s while trying to stat %s\n", + program_invocation_short_name, strerror (errno), + subpath); + else + { + if (S_ISREG (st.st_mode) || S_ISDIR (st.st_mode)) + chattr (subpath); + if (S_ISDIR (st.st_mode) && Ropt) + chattr_dir (subpath); + } + } + free (subpath); + return 0; +} + +static void +print_version () +{ + printf ("%s (cygwin) %d.%d.%d\n" + "Get POSIX ACL information\n" + "Copyright (C) 2018 - %s Cygwin Authors\n" + "This is free software; see the source for copying conditions. " + "There is NO\n" + "warranty; not even for MERCHANTABILITY or FITNESS FOR A " + "PARTICULAR PURPOSE.\n", + program_invocation_short_name, + CYGWIN_VERSION_DLL_MAJOR / 1000, + CYGWIN_VERSION_DLL_MAJOR % 1000, + CYGWIN_VERSION_DLL_MINOR, + strrchr (__DATE__, ' ') + 1); +} + +static void +usage (FILE *stream) +{ + fprintf (stream, "Usage: %s [-RVfhv] [+-=mode]... [file]...\n", + program_invocation_short_name); + if (stream == stderr) + fprintf (stream, "Try '%s --help' for more information\n", + program_invocation_short_name); + if (stream == stdout) + fprintf (stream, "\n" + "Change file attributes\n" + "\n" + " -R, --recursive recursively list attributes of directories and their \n" + " contents\n" + " -V, --verbose Be verbose during operation\n" + " -f, --force suppress error messages\n" + " -h, --help this help text\n" + " -v, --version display the program version\n" + "\n" + "The format of 'mode' is {+-=}[acCehnrsSt]\n" + "\n" + "The operator '+' causes the selected attributes to be added to the\n" + "existing attributes of the files; '-' causes them to be removed; and\n" + "'=' causes them to be the only attributes that the files have.\n" + "\n" + "Supported attributes:\n" + "\n" + " 'r', 'Readonly': file is read-only\n" + " 'h', 'Hidden': file or directory is hidden\n" + " 's', 'System': file or directory that the operating system uses\n" + " 'a', 'Archive': file or directory has the archive marker set\n" + " 't', 'Temporary': file is being used for temporary storage\n" + " 'S', 'Sparse': file is sparse\n" + " 'c', 'Compressed': file or directory is compressed\n" + " 'n', 'Notindexed': file or directory is not to be indexed by the\n" + " content indexing service\n" + " 'e', 'Encrypted': file is encrypted\n" + " 'C', 'Casesensitive': directory is handled case sensitive\n" + " (Windows 10 1803 or later, local NTFS only,\n" + " WSL must be installed)\n"); +} + +int +main (int argc, char **argv) +{ + int c, ret = 0; + int lastoptind = 0; + char *opt; + + opterr = 0; + while ((c = getopt_long (argc, argv, opts, longopts, NULL)) != EOF) + { + switch (c) + { + case 'R': + Ropt = 1; + lastoptind = optind; + break; + case 'V': + Vopt = 1; + lastoptind = optind; + break; + case 'f': + fopt = 1; + lastoptind = optind; + break; + case 'v': + print_version (); + return 0; + break; + default: + if (optind > lastoptind) + { + --optind; + goto next; + } + /*FALLTHRU*/ + case 'h': + usage (c == 'h' ? stdout : stderr); + return 1; + } + } +next: + while (optind < argc) + { + if (strcmp (argv[optind], "--") == 0) + { + ++optind; + break; + } + opt = strchr ("+-=", argv[optind][0]); + if (!opt) + break; + if (argv[optind][1] == '\0' || get_flags (argv[optind])) + { + usage (stderr); + return 1; + } + ++optind; + } + if (sanity_check ()) + return 1; + if (optind > argc - 1) + { + chattr ("."); + if (Ropt) + chattr_dir ("."); + } + else for (; optind < argc; ++optind) + { + struct stat st; + + if (lstat (argv[optind], &st) != 0) + { + fprintf (stderr, "%s: %s while trying to stat %s\n", + program_invocation_short_name, strerror (errno), + argv[optind]); + ret = 1; + } + else if (!S_ISREG (st.st_mode) && !S_ISDIR (st.st_mode)) + { + fprintf (stderr, "%s: %s on %s\n", + program_invocation_short_name, strerror (ENOTSUP), + argv[optind]); + ret = 1; + } + else + { + if (chattr (argv[optind])) + ret = 1; + if (S_ISDIR (st.st_mode) && chattr_dir (argv[optind])) + ret = 1; + } + } + return ret; +} diff --git a/winsup/utils/lsattr.c b/winsup/utils/lsattr.c new file mode 100644 index 000000000..ee72043f0 --- /dev/null +++ b/winsup/utils/lsattr.c @@ -0,0 +1,289 @@ +/* lsattr.c + + Written by Corinna Vinschen + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int Ropt, aopt, dopt, lopt, nopt; + +struct option longopts[] = { + { "recursive", no_argument, NULL, 'R' }, + { "version", no_argument, NULL, 'V' }, + { "all", no_argument, NULL, 'a' }, + { "directory", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "long", no_argument, NULL, 'l' }, + { "no-headers", no_argument, NULL, 'n' }, + { NULL, no_argument, NULL, 0} +}; + +const char *opts = "+RVadhln"; + +struct +{ + uint64_t flagval; + char chr; + const char *str; +} supp_flag[] = { + { FS_READONLY_FL, 'r', "Readonly" }, + { FS_HIDDEN_FL, 'h', "Hidden" }, + { FS_SYSTEM_FL, 's', "System" }, + { FS_ARCHIVE_FL, 'a', "Archive" }, + { FS_TEMP_FL, 't', "Temporary" }, + { FS_SPARSE_FL, 'S', "Sparse" }, + { FS_REPARSE_FL, 'r', "Reparse" }, + { FS_COMPRESSED_FL, 'c', "Compressed" }, + { FS_OFFLINE_FL, 'o', "Offline" }, + { FS_NOTINDEXED_FL, 'n', "Notindexed" }, + { FS_ENCRYPT_FL, 'e', "Encrypted" }, + { FS_CASESENS_FL, 'C', "Casesensitive" }, + { 0, '\0', NULL }, +}; + +void +print_long (const char *path, uint64_t flags) +{ + int i; + int first = 1; + + printf("%-28s ", path); + for (i = 0; supp_flag[i].flagval; ++i) + if (flags & supp_flag[i].flagval) + { + if (!first) + fputs (", ", stdout); + first = 0; + fputs (supp_flag[i].str, stdout); + } + if (first) + fputs ("---", stdout); + fputc ('\n', stdout); +} + +void +print_short (const char *path, uint64_t flags) +{ + int i; + + for (i = 0; supp_flag[i].flagval; ++i) + fputc ((flags & supp_flag[i].flagval) ? supp_flag[i].chr : '-', stdout); + printf(" %s\n", path); +} + +int +lsattr (const char *path) +{ + int fd; + uint64_t flags; + + fd = open (path, O_RDONLY); + if (fd < 0) + { + fprintf (stderr, "%s: %s while trying to open %s\n", + program_invocation_short_name, strerror (errno), + path); + return 1; + } + if (ioctl (fd, FS_IOC_GETFLAGS, &flags)) + { + close (fd); + fprintf (stderr, "%s: %s while trying to fetch flags from %s\n", + program_invocation_short_name, strerror (errno), + path); + return 1; + } + close (fd); + if (lopt) + print_long (path, flags); + else + print_short (path, flags); + return 0; +} + +int +lsattr_dir (const char *path) +{ + DIR *dir; + struct dirent *de; + char *subpath = (char *) malloc (strlen (path) + 1 + NAME_MAX + 1); + char *comp; + + dir = opendir (path); + if (!dir) + { + free (subpath); + return 1; + } + comp = stpcpy (subpath, path); + if (comp[-1] != '/') + *comp++ = '/'; + while ((de = readdir (dir))) + { + struct stat st; + + stpcpy (comp, de->d_name); + if (lstat (subpath, &st) != 0) + fprintf (stderr, "%s: %s while trying to stat %s\n", + program_invocation_short_name, strerror (errno), + subpath); + else if (de->d_name[0] != '.' || aopt) + { + if (S_ISREG (st.st_mode) || S_ISDIR (st.st_mode)) + lsattr (subpath); + if (S_ISDIR (st.st_mode) && Ropt + && strcmp (de->d_name, ".") != 0 + && strcmp (de->d_name, "..") != 0) + { + if (!nopt) + printf ("\n%s:\n", path); + lsattr_dir (subpath); + if (!nopt) + fputc ('\n', stdout); + } + } + } + free (subpath); + return 0; +} + +static void +print_version () +{ + printf ("%s (cygwin) %d.%d.%d\n" + "Get POSIX ACL information\n" + "Copyright (C) 2018 - %s Cygwin Authors\n" + "This is free software; see the source for copying conditions. " + "There is NO\n" + "warranty; not even for MERCHANTABILITY or FITNESS FOR A " + "PARTICULAR PURPOSE.\n", + program_invocation_short_name, + CYGWIN_VERSION_DLL_MAJOR / 1000, + CYGWIN_VERSION_DLL_MAJOR % 1000, + CYGWIN_VERSION_DLL_MINOR, + strrchr (__DATE__, ' ') + 1); +} + +static void +usage (FILE *stream) +{ + fprintf (stream, "Usage: %s [-RVadhln] [file]...\n", + program_invocation_short_name); + if (stream == stderr) + fprintf (stream, "Try '%s --help' for more information\n", + program_invocation_short_name); + if (stream == stdout) + fprintf (stream, "\n" + "List file attributes\n" + "\n" + " -R, --recursive recursively list attributes of directories and their \n" + " contents\n" + " -V, --version display the program version\n" + " -a, --all list all files in directories, including files that\n" + " start with '.'\n" + " -d, --directory list directories like other files, rather than listing\n" + " their contents.\n" + " -l, --long print options using long names instead of single\n" + " character abbreviations\n" + " -n, --no-headers don't print directory headers when recursing\n" + " -h, --help this help text\n" + "\n" + "Supported attributes:\n" + "\n" + " 'r', 'Readonly': file is read-only, directory is system-marked\n" + " 'h', 'Hidden': file or directory is hidden\n" + " 's', 'System': file or directory that the operating system uses\n" + " 'a', 'Archive': file or directory has the archive marker set\n" + " 't', 'Temporary': file is being used for temporary storage\n" + " 'S', 'Sparse': file is sparse\n" + " 'r', 'Reparse': file or directory that has a reparse point\n" + " 'c', 'Compressed': file or directory is compressed\n" + " 'o', 'Offline': the data of a file is moved to offline storage\n" + " 'n', 'Notindexed': file or directory is not to be indexed by the\n" + " content indexing service\n" + " 'e', 'Encrypted': file is encrypted\n" + " 'C', 'Casesensitive': directory is handled case sensitive\n" + " (Windows 10 1803 or later, local NTFS only,\n" + " WSL must be installed)\n"); +} + +int +main (int argc, char **argv) +{ + int c, ret = 0; + + opterr = 0; + while ((c = getopt_long (argc, argv, opts, longopts, NULL)) != EOF) + { + switch (c) + { + case 'R': + Ropt = 1; + break; + case 'V': + print_version (); + return 0; + case 'a': + aopt = 1; + break; + case 'd': + dopt = 1; + break; + case 'l': + lopt = 1; + break; + case 'n': + nopt = 1; + break; + case 'h': + default: + usage (c == 'h' ? stdout : stderr); + return 1; + } + } + if (optind > argc - 1) + lsattr_dir ("."); + else for (; optind < argc; ++optind) + { + struct stat st; + + if (lstat (argv[optind], &st) != 0) + { + fprintf (stderr, "%s: %s while trying to stat %s\n", + program_invocation_short_name, strerror (errno), + argv[optind]); + ret = 1; + } + else if (!S_ISREG (st.st_mode) && !S_ISDIR (st.st_mode)) + { + fprintf (stderr, "%s: %s on %s\n", + program_invocation_short_name, strerror (ENOTSUP), + argv[optind]); + ret = 1; + } + else if (S_ISDIR (st.st_mode) && !dopt) + { + if (lsattr_dir (argv[optind])) + ret = 1; + } + else if (lsattr (argv[optind])) + ret = 1; + } + return ret; +} -- 2.43.5