21

Well, to be specific, it was chmod -R 755. Now every file is executable, which I don't want. I am thinking that I should look at the first two bytes of each file for the #!, but will this cover everything? Should I instead use file to look at everything and base my decision on that? Or, more likely, is there an even better way to do this?

What is the preferred way to recursively go through a directory and set -x on files that are not 'supposed to be' executable?

Larry Wang
  • 2,675
  • 4
  • 20
  • 11

3 Answers3

17

There's no magic bullet here. The permissions carry information which is not always redundant.

If you'd done this in a system directory, your system would be in a very bad state, because you'd have to worry about setuid and setgid bits, and about files that are not supposed to be world-readable, and about files that are supposed to be group- or world-writable.

In a per-user directory, you have to worry about files that aren't supposed to be world-readable. No one can help you there.

As for executability, a good rule of thumb would be to make everything that doesn't look like it could be executed, be nonexecutable. The kernel can execute scripts whose first two bytes are #!, ELF binaries whose first four bytes are \x7fELF where \x7f is the byte with the value 12, and a few rarer file types (a.out, anything registered with binfmt_misc). Hence the following command should restore your permissions to a reasonable state (assumes bash 4 or zsh, otherwise use find to traverse the directory tree; warning, typed directly into the browser):

for x in **/*; do
  if ! [ -f "$x" ]; then continue; fi # skip all but regular files
  case $(head -c 4 "$x") in
    "#!"??) :;; # skip script
    "\x7fELF") :;; # skip ELF executable
    *) chmod a-x "$x";;
  esac
done

Note that there is a simple way to back up and restore permissions of a directory tree, on Linux and possibly other unices with ACL support:

getfacl -R >saved-permissions
setfacl --restore=saved-permissions
  • Thanks! Fortunately, I think everything falls into these two categories. If this misses anything, I can deal with it later. – Larry Wang Aug 27 '10 at 21:35
  • Bear in mind that **/* requires globstar. – Chris Down Nov 07 '11 at 14:03
  • I'd recommend two changes to this script. One, use find rather than globstar; two, instead of looking at the head, use the file command to see what it is and branch from there. – Shadur-don't-feed-the-AI Nov 12 '11 at 15:52
  • @Shadur find is less reliable than globstar, globstar is preferable in almost every case. – Chris Down Nov 15 '11 at 21:58
  • @ChrisDown Whoa! find and globstar each have their good and bad points in terms of reliability, portability, clarity, efficiency, … – Gilles 'SO- stop being evil' Nov 15 '11 at 22:10
  • 1
    @Gilles Preferable as in "if you have it, it's far superior", not only is it faster, it's also more reliable (and doesn't have unexpected SNAFUs). – Chris Down Nov 15 '11 at 22:11
  • @ChrisDown Not always. With **/*, you need to remember to use -- in case a filename in the current directory begins with -. And you risk running into the command line length limit. While find | … is dodgy because of newlines in filenames, find -exec avoids most reliability issues. P.S. This is getting off-topic; continue in chat if you want. – Gilles 'SO- stop being evil' Nov 15 '11 at 22:16
8

I believe you will want something like

find dir -type f -exec chmod ugo-x '{}' +

This looks for all regular files, recursively in dir, (it excludes directories, and devices) and removes the executable bit.

I would start here, and then work my way towards making files that are supposed to be executable, executable.

The following should work exactly as you asked for ( it will find all regular files, grep them for #! and then remove the x bits if not found )

find . -type f | xargs grep -L #! | xargs chmod ugo-x

possibly a better version of the above (less pipes)

find . -type f -exec grep -L #! '{}' + | xargs chmod ugo-x 
xenoterracide
  • 59,188
  • 74
  • 187
  • 252
  • 3
    Make that grep -L '^#!' at least (the quotes are necessary, and ^ restricts to matching at the beginning of the line), but it's still too permissive as it matches #! on any line. Using xargs will fail with file names containing spaces or quoting characters; use xargs -d '\n' (requires GNU xargs). – Gilles 'SO- stop being evil' Aug 27 '10 at 21:30
0

Well, without a shebang line, the file will be executed as a shell script, nominally with /bin/sh. You're idea is a good start and on the assumption that the directory in question doesn't contain mission-critical files there probably isn't much risk to executing some grep and chmod combo. You may encounter false positives, i.e., files with a shebang line that are not meant to have their executable bit set but without knowing more information on the purpose of what's in the directory, only you can decide whether that poses a significant existential threat to your system and/or data.

gvkv
  • 2,738
  • I'm not worried so much about false positives as false negatives. There are binaries that I don't think start with #!. – Larry Wang Aug 27 '10 at 19:48