What if I accidentally run command "chmod -R" on system directories (/, /etc, ...)
In short: you can't, reinstall your system.
I mean, Posix permissions are used and relied on heavily; there's a multitude of places in the filesystem where wrong permissions would break the OS (SUID flags) or even worse, make it exposed security-wise (/etc/ssh/ssh_host_rsa_key
) while it appears to be working OK.
Hence, such a recovery is hard to do properly. Miss one thing — and you screw it up. You already screwed up your sudo chmod
command (if that's your friend rather than you, she might as well learn some Linux lesson, too) — and that's a very simple of a command. Proper recovery would demand way more commands and way more vigilance. Even if you use someone's script.
So trust me, just reinstall. It's a safe bet and guaranteed to keep you out of trouble.
Finally, some tips relevant here.
First: reinstalls will be less painful if you setup your /home
on a separate partition next time. Actually, they will be a breeze.
Second: consider doing crazy Linux science in a virtual machine like the VirtualBox, and do your snapshots.
Third: chmod -R .
works. A dot by itself .
is valid directory name. There's no real need to append that slash. You could've avoided the catastrophic risk of skipping the dot entrirely;
mere chmod: missing operand after ‘755’
VS a ruined system.
I wrote and have been using for several years a couple of Ruby scripts to rsync
permissions and ownership. Script get-filesystem-acl
collects all the information by recursively traversing all the files and puts it all into the file .acl
. Script .acl-restore
will read .acl
and apply all the chown
's and chmod
's.
You can run get-filesystem-acl
on a similar Ubuntu installation and then copy over the .acl
file to your chmod-damaged box, put .acl
and .acl-restore
in /, and run .acl-restore
.
You will need to have root so fix your sudo
as Marco Ceppi suggested.
I can generate and give you the .acl
file for my Ubuntu.
get-filesystem-acl
#!/usr/bin/ruby
RM = "/bin/rm"
SORT = "/usr/bin/sort"
TMP = "/tmp/get_acl_#{Time.now.to_i}_#{rand * 899 + 100}"
require 'find'
IGNORE = [".git"]
def numeric2human(m)
return sprintf("%c%c%c%c%c%c%c%c%c",
(m & 0400 == 0 ? ?- : ?r),
(m & 0200 == 0 ? ?- : ?w),
(m & 0100 == 0 ? (m & 04000 == 0 ? ?- : ?S) :
(m & 04000 == 0 ? ?x : ?s)),
(m & 0040 == 0 ? ?- : ?r),
(m & 0020 == 0 ? ?- : ?w),
(m & 0010 == 0 ? (m & 02000 == 0 ? ?- : ?S) :
(m & 02000 == 0 ? ?x : ?s)),
(m & 0004 == 0 ? ?- : ?r),
(m & 0002 == 0 ? ?- : ?w),
(m & 0001 == 0 ? (m & 01000 == 0 ? ?- : ?T) :
(m & 01000 == 0 ? ?x : ?t)))
end
File.open(TMP, "w") do |acl_file|
# TODO: Instead of the current dir, find the .git dir, which could be
# the same or outside of the current dir
Find.find(".") do |path|
next if IGNORE.collect {|ig| !!(path[2..-1] =~ /\A#{ig}/)}.include? true
next if File.symlink?(path)
stat = File.lstat(path)
group_id = stat.gid
rules = "#{type}#{numeric2human(stat.mode)}"
acl_file.puts "#{path} #{rules} #{owner_id} #{group_id}"
end
end
`#{SORT} #{TMP} > .acl`
`#{RM} #{TMP}`
.acl-restore
#!/usr/bin/ruby
# This script will only work with .acl_ids
# Restore from...
FROM = ".acl"
MKDIR = "/bin/mkdir"
CHMOD = "/bin/chmod"
CHOWN = "/bin/chown"
known_content_missing = false
def numeric2human(m)
return sprintf("%c%c%c%c%c%c%c%c%c",
(m & 0400 == 0 ? ?- : ?r),
(m & 0200 == 0 ? ?- : ?w),
(m & 0100 == 0 ? (m & 04000 == 0 ? ?- : ?S) :
(m & 04000 == 0 ? ?x : ?s)),
(m & 0040 == 0 ? ?- : ?r),
(m & 0020 == 0 ? ?- : ?w),
(m & 0010 == 0 ? (m & 02000 == 0 ? ?- : ?S) :
(m & 02000 == 0 ? ?x : ?s)),
(m & 0004 == 0 ? ?- : ?r),
(m & 0002 == 0 ? ?- : ?w),
(m & 0001 == 0 ? (m & 01000 == 0 ? ?- : ?T) :
(m & 01000 == 0 ? ?x : ?t)))
end
def human2chmod(mode)
raise unless mode =~ /([r-][w-][xtsTS-])([r-][w-][xtsTS-])([r-][w-][xtsTS-])/
triple = [$1, $2, $3]
u,g,o = triple.collect do |i|
i.sub('s', 'sx').sub('t', 'tx').downcase.gsub('-', '')
end
return "u=#{u},g=#{g},o=#{o}"
end
File.open(FROM).each do |acl|
raise unless acl =~ /\A(([^ ]*? )+)([^ ]+) ([^ ]+) ([^ ]+)\Z/
path, rules, owner_id, group_id = $1, $3, $4, $5
path = path.strip
owner_id = owner_id.to_i
group_id = group_id.to_i
if !File.exists?(path) and !File.symlink?(path)
if rules =~ /\Ad/
STDERR.puts "Restoring a missing directory: #{path}"
STDERR.puts "Probably it was an empty directory. Git goes not track them."
`#{MKDIR} -p '#{path}'` # Creating the any parents
else
known_content_missing = true
STDERR.puts "ERROR: ACL is listed but the file is missing: #{path}"
next
end
end
s = File.lstat(path)
t = s.ftype[0..0].sub('f', '-') # Single character for the file type
# But a "-" istead of "f"
# Actual, but not neccesarely Desired
actual_rules = "#{t}#{numeric2human(s.mode)}"
actual_owner_id = s.uid
actual_group_id = s.gid
unless [actual_rules, actual_owner_id, actual_group_id] ==
[rules, owner_id, group_id]
chmod_argument = human2chmod(rules)
# Debug
#p chmod_argument
#p s.mode
## Verbose
puts path
puts "Wrong: #{[actual_rules, actual_owner_id, actual_group_id].inspect}"
puts "Fixed: #{[rules, owner_id, group_id].inspect}"
`#{CHMOD} #{chmod_argument} '#{path}'`
#puts
end
end
if known_content_missing
STDERR.puts "-" * 80
STDERR.puts "Some files that are listed in #{FROM.inspect} are missing in " +
"the current directory."
STDERR.puts
STDERR.puts "Is #{FROM.inspect} outdated?"
STDERR.puts "(Try retrograding the current directory to an earlier version)"
STDERR.puts
STDERR.puts "Or is the current directory incomplete?"
STDERR.puts "(Try to recover the current directory)"
STDERR.puts "-" * 80
end
In long: you can. You'll need to mount the the file system from the a Live CD and begin reverting the permissions in the appropriate places. At a minimum to get sudo back you'll want to run sudo chmod u+s /usr/bin/sudo
while in the LiveCD session - that will fix the must be setuid root.
However, it would likely be easier to simply reinstall the system.
I would try to reinstall all packages with apt-get install --reinstall
, possibly using the output of dpkg --get-selections | grep install
to get a list of them.
Alright, I haven't tested this (so use at your own risk), but it still might work. I Will test this in a virtual machine when I get the chance to:
First, in a still working system, I did the following to get all file permissions in a list, skipping the /home/
directory:
sudo find / -not -path /home -printf "%m:%p\0" > /tmp/fileper.log
This will print the permissions and file name for each file or directory on the system, followed by a \0
character (this is needed later on to deal with weird file names such as those containing newlines).
Then, on a system where the file permissions have been compromised:
while IFS=: read -r -d '' perm file; do
chmod "$perm" "$file"
done < /tmp/fileper.log
This will read each line of fileper.log
, saving the permissions as$perm
and the file name as $file
and then will set the file (or directory's) permissions to whatever was listed in the fileper.log
A few things to note here:
- While outputting to the file:
/tmp/fileper.log
, you might be listing custom settings, and proc, etc. - you might not be able to boot, or run commands,
What I would suggest is boot up a LiveCD with the Linux version you have on your disk, run the command, modify the path to where you have the local disk mounted, and run the second command!
I have tested that when booted from an Ubuntu CD/USB, I can choose not to format disk, meaning it will replace everything in the /
directory, BUT skip the /home/
directory. Meaning your users will have the configuration of apps/DATA(Music,Video,Documents) still intact. And by replacing the system files, the chmod
is set to there proper number.