/* 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; int set_used; 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_PINNED_FL, 'p', "Pinned" }, { FS_UNPINNED_FL, 'u', "Unpinned" }, { FS_CASESENS_FL, 'C', "Casesensitive" }, { 0, '\0', NULL }, }; const char *supp_list = "rhsatSrconepuC"; 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; set_used = 1; 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_used && !add && !del) fprintf (stderr, "%s: Must use at least one of =, + or -\n", program_invocation_short_name); else if (set_used && (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_used) 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" "Change file attributes\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 __attribute__ ((__noreturn__)) 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 apply the changes to 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 {+-=}[acCehnprsStu]\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" "A single '=' causes all attributes to be removed.\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" " 'p', 'Pinned': file is pinned\n" " 'u', 'Unpinned': file is unpinned\n" " 'C', 'Casesensitive': directory is handled case sensitive\n"); exit (stream == stdout ? 0 : 1); } int main (int argc, char **argv) { int c, ret = 0; int lastoptind = 1; char *opt; opterr = 0; while ((c = getopt_long (argc, argv, opts, longopts, NULL)) != EOF) { switch (c) { case 'R': Ropt = 1; break; case 'V': Vopt = 1; break; case 'f': fopt = 1; break; case 'H': usage (stdout); break; case 'v': print_version (); return 0; break; default: if (optind > lastoptind) --optind; goto next; } lastoptind = optind; } next: while (optind < argc) { if (strcmp (argv[optind], "--") == 0) { ++optind; break; } opt = strchr ("+-=", argv[optind][0]); if (!opt) break; if ((*opt != '=' && argv[optind][1] == '\0') || get_flags (argv[optind])) usage (stderr); ++optind; } if (sanity_check ()) return 1; if (optind > argc - 1) usage (stderr); 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) && Ropt && chattr_dir (argv[optind])) ret = 1; } } return ret; }