Rewrite of ensure-dirs in python
[ganeti-local] / lib / tools / ensure_dirs.py
1 # Copyright (C) 2011 Google Inc.
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 # General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16 # 02110-1301, USA.
17
18 """Script to ensure permissions on files/dirs are accurate.
19
20 """
21
22 import errno
23 import os
24 import os.path
25 import optparse
26 import sys
27 import stat
28
29 from ganeti import constants
30 from ganeti import errors
31 from ganeti import runtime
32 from ganeti import ssconf
33
34
35 (DIR, FILE) = range(2)
36 ALL_TYPES = frozenset([DIR, FILE])
37
38
39 class EnsureError(errors.GenericError):
40   """Top-level error class related to this script.
41
42   """
43
44
45 def EnsurePermission(path, mode, uid=-1, gid=-1, must_exist=True,
46                      _chmod_fn=os.chmod, _chown_fn=os.chown):
47   """Ensures that given path has given mode.
48
49   @param path: The path to the file
50   @param mode: The mode of the file
51   @param uid: The uid of the owner of this file
52   @param gid: The gid of the owner of this file
53   @param must_exist: Specifies if non-existance of path will be an error
54   @param _chmod_fn: chmod function to use (unittest only)
55   @param _chown_fn: chown function to use (unittest only)
56
57   """
58   try:
59     _chmod_fn(path, mode)
60
61     if max(uid, gid) > -1:
62       _chown_fn(path, uid, gid)
63   except EnvironmentError, err:
64     if err.errno == errno.ENOENT:
65       if must_exist:
66         raise EnsureError("Path %s does not exists, but should" % path)
67     else:
68       raise EnsureError("Error while changing permission on %s: %s" %
69                         (path, err))
70
71
72 def EnsureDir(path, mode, uid, gid, _stat_fn=os.lstat, _mkdir_fn=os.mkdir,
73               _ensure_fn=EnsurePermission):
74   """Ensures that given path is a dir and has given mode, uid and gid set.
75
76   @param path: The path to the file
77   @param mode: The mode of the file
78   @param uid: The uid of the owner of this file
79   @param gid: The gid of the owner of this file
80   @param _stat_fn: Stat function to use (unittest only)
81   @param _mkdir_fn: mkdir function to use (unittest only)
82   @param _ensure_fn: ensure function to use (unittest only)
83
84   """
85   try:
86     # We don't want to follow symlinks
87     st_mode = _stat_fn(path)[stat.ST_MODE]
88
89     if not stat.S_ISDIR(st_mode):
90       raise EnsureError("Path %s is expected to be a directory, but it's not" %
91                         path)
92   except EnvironmentError, err:
93     if err.errno == errno.ENOENT:
94       _mkdir_fn(path)
95     else:
96       raise EnsureError("Error while do a stat() on %s: %s" % (path, err))
97
98   _ensure_fn(path, mode, uid=uid, gid=gid)
99
100
101 def RecursiveEnsure(path, uid, gid, dir_perm, file_perm):
102   """Ensures permissions recursively down a directory.
103
104   This functions walks the path and sets permissions accordingly.
105
106   @param path: The absolute path to walk
107   @param uid: The uid used as owner
108   @param gid: The gid used as group
109   @param dir_perm: The permission bits set for directories
110   @param file_perm: The permission bits set for files
111
112   """
113   assert os.path.isabs(path), "Path %s is not absolute" % path
114   assert os.path.isdir(path), "Path %s is not a dir" % path
115
116   for root, dirs, files in os.walk(path):
117     for subdir in dirs:
118       EnsurePermission(os.path.join(root, subdir), dir_perm, uid=uid, gid=gid)
119
120     for filename in files:
121       EnsurePermission(os.path.join(root, filename), file_perm, uid=uid,
122                        gid=gid)
123
124
125 def ProcessPath(path):
126   """Processes a path component.
127
128   @param path: A tuple of the path component to process
129
130   """
131   (pathname, pathtype, mode, uid, gid) = path[0:5]
132
133   assert pathtype in ALL_TYPES
134
135   if pathtype == DIR:
136     # No additional parameters
137     assert len(path[5:]) == 0
138     EnsureDir(pathname, mode, uid, gid)
139   elif pathtype == FILE:
140     (must_exist, ) = path[5:]
141     EnsurePermission(pathname, mode, uid=uid, gid=gid, must_exist=must_exist)
142
143
144 def GetPaths():
145   """Returns a tuple of path objects to process.
146
147   """
148   getent = runtime.GetEnts()
149   masterd_log = constants.DAEMONS_LOGFILES[constants.MASTERD]
150   noded_log = constants.DAEMONS_LOGFILES[constants.NODED]
151   confd_log = constants.DAEMONS_LOGFILES[constants.CONFD]
152   rapi_log = constants.DAEMONS_LOGFILES[constants.RAPI]
153
154   rapi_dir = os.path.join(constants.DATA_DIR, "rapi")
155
156   paths = [
157     (constants.DATA_DIR, DIR, 0755, getent.masterd_uid,
158      getent.masterd_gid),
159     (constants.CLUSTER_DOMAIN_SECRET_FILE, FILE, 0640,
160      getent.masterd_uid, getent.masterd_gid, False),
161     (constants.CLUSTER_CONF_FILE, FILE, 0640, getent.masterd_uid,
162      getent.confd_gid, False),
163     (constants.CONFD_HMAC_KEY, FILE, 0440, getent.confd_uid,
164      getent.masterd_gid, False),
165     (constants.SSH_KNOWN_HOSTS_FILE, FILE, 0644, getent.masterd_uid,
166      getent.masterd_gid, False),
167     (constants.RAPI_CERT_FILE, FILE, 0440, getent.rapi_uid,
168      getent.masterd_gid, False),
169     (constants.NODED_CERT_FILE, FILE, 0440, getent.masterd_uid,
170      getent.masterd_gid, False),
171     ]
172
173   ss = ssconf.SimpleStore()
174   for ss_path in ss.GetFileList():
175     paths.append((ss_path, FILE, 0400, getent.noded_uid, 0, False))
176
177   paths.extend([
178     (constants.QUEUE_DIR, DIR, 0700, getent.masterd_uid,
179      getent.masterd_gid),
180     (constants.JOB_QUEUE_SERIAL_FILE, FILE, 0600,
181      getent.masterd_uid, getent.masterd_gid, False),
182     (constants.JOB_QUEUE_ARCHIVE_DIR, DIR, 0700,
183      getent.masterd_uid, getent.masterd_gid),
184     (rapi_dir, DIR, 0750, getent.rapi_uid, getent.masterd_gid),
185     (constants.RAPI_USERS_FILE, FILE, 0640, getent.rapi_uid,
186      getent.masterd_gid, False),
187     (constants.RUN_GANETI_DIR, DIR, 0775, getent.masterd_uid,
188      getent.daemons_gid),
189     (constants.SOCKET_DIR, DIR, 0750, getent.masterd_uid,
190      getent.daemons_gid),
191     (constants.MASTER_SOCKET, FILE, 0770, getent.masterd_uid,
192      getent.daemons_gid, False),
193     (constants.BDEV_CACHE_DIR, DIR, 0755, getent.noded_uid,
194      getent.masterd_gid),
195     (constants.UIDPOOL_LOCKDIR, DIR, 0750, getent.noded_uid,
196      getent.masterd_gid),
197     (constants.DISK_LINKS_DIR, DIR, 0755, getent.noded_uid,
198      getent.masterd_gid),
199     (constants.CRYPTO_KEYS_DIR, DIR, 0700, getent.noded_uid,
200      getent.masterd_gid),
201     (constants.IMPORT_EXPORT_DIR, DIR, 0755, getent.noded_uid,
202      getent.masterd_gid),
203     (constants.LOG_DIR, DIR, 0770, getent.masterd_uid,
204      getent.daemons_gid),
205     (masterd_log, FILE, 0600, getent.masterd_uid, getent.masterd_gid,
206      False),
207     (confd_log, FILE, 0600, getent.confd_uid, getent.masterd_gid, False),
208     (noded_log, FILE, 0600, getent.noded_uid, getent.masterd_gid, False),
209     (rapi_log, FILE, 0600, getent.rapi_uid, getent.masterd_gid, False),
210     (constants.LOG_OS_DIR, DIR, 0750, getent.masterd_uid,
211      getent.daemons_gid),
212     ])
213
214   return tuple(paths)
215
216
217 def ParseOptions():
218   """Parses the options passed to the program.
219
220   @return: Options and arguments
221
222   """
223   program = os.path.basename(sys.argv[0])
224
225   parser = optparse.OptionParser(usage="%%prog [--full-run]",
226                                  prog=program)
227   parser.add_option("--full-run", "-f", dest="full_run", action="store_true",
228                     default=False, help=("Make a full run and collect"
229                                          " additional files (time consuming)"))
230
231   return parser.parse_args()
232
233
234 def Main():
235   """Main routine.
236
237   """
238   getent = runtime.GetEnts()
239   (opts, _) = ParseOptions()
240
241   try:
242     for path in GetPaths():
243       ProcessPath(path)
244
245     if opts.full_run:
246       RecursiveEnsure(constants.JOB_QUEUE_ARCHIVE_DIR, getent.masterd_uid,
247                       getent.masterd_gid, 0700, 0600)
248   except EnsureError, err:
249     print >> sys.stderr, "An error occurred while ensure permissions:", err
250     return constants.EXIT_FAILURE
251
252   return constants.EXIT_SUCCESS