Statistics
| Branch: | Tag: | Revision:

root / lib / utils / io.py @ b81b3c96

History | View | Annotate | Download (23.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21
"""Utility functions for I/O.
22

23
"""
24

    
25
import os
26
import logging
27
import shutil
28
import tempfile
29
import errno
30
import time
31
import stat
32

    
33
from ganeti import errors
34
from ganeti import constants
35
from ganeti.utils import filelock
36

    
37

    
38
#: Path generating random UUID
39
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
40

    
41

    
42
def ReadFile(file_name, size=-1, preread=None):
43
  """Reads a file.
44

45
  @type size: int
46
  @param size: Read at most size bytes (if negative, entire file)
47
  @type preread: callable receiving file handle as single parameter
48
  @param preread: Function called before file is read
49
  @rtype: str
50
  @return: the (possibly partial) content of the file
51

52
  """
53
  f = open(file_name, "r")
54
  try:
55
    if preread:
56
      preread(f)
57

    
58
    return f.read(size)
59
  finally:
60
    f.close()
61

    
62

    
63
def WriteFile(file_name, fn=None, data=None,
64
              mode=None, uid=-1, gid=-1,
65
              atime=None, mtime=None, close=True,
66
              dry_run=False, backup=False,
67
              prewrite=None, postwrite=None):
68
  """(Over)write a file atomically.
69

70
  The file_name and either fn (a function taking one argument, the
71
  file descriptor, and which should write the data to it) or data (the
72
  contents of the file) must be passed. The other arguments are
73
  optional and allow setting the file mode, owner and group, and the
74
  mtime/atime of the file.
75

76
  If the function doesn't raise an exception, it has succeeded and the
77
  target file has the new contents. If the function has raised an
78
  exception, an existing target file should be unmodified and the
79
  temporary file should be removed.
80

81
  @type file_name: str
82
  @param file_name: the target filename
83
  @type fn: callable
84
  @param fn: content writing function, called with
85
      file descriptor as parameter
86
  @type data: str
87
  @param data: contents of the file
88
  @type mode: int
89
  @param mode: file mode
90
  @type uid: int
91
  @param uid: the owner of the file
92
  @type gid: int
93
  @param gid: the group of the file
94
  @type atime: int
95
  @param atime: a custom access time to be set on the file
96
  @type mtime: int
97
  @param mtime: a custom modification time to be set on the file
98
  @type close: boolean
99
  @param close: whether to close file after writing it
100
  @type prewrite: callable
101
  @param prewrite: function to be called before writing content
102
  @type postwrite: callable
103
  @param postwrite: function to be called after writing content
104

105
  @rtype: None or int
106
  @return: None if the 'close' parameter evaluates to True,
107
      otherwise the file descriptor
108

109
  @raise errors.ProgrammerError: if any of the arguments are not valid
110

111
  """
112
  if not os.path.isabs(file_name):
113
    raise errors.ProgrammerError("Path passed to WriteFile is not"
114
                                 " absolute: '%s'" % file_name)
115

    
116
  if [fn, data].count(None) != 1:
117
    raise errors.ProgrammerError("fn or data required")
118

    
119
  if [atime, mtime].count(None) == 1:
120
    raise errors.ProgrammerError("Both atime and mtime must be either"
121
                                 " set or None")
122

    
123
  if backup and not dry_run and os.path.isfile(file_name):
124
    CreateBackup(file_name)
125

    
126
  # Whether temporary file needs to be removed (e.g. if any error occurs)
127
  do_remove = True
128

    
129
  # Function result
130
  result = None
131

    
132
  (dir_name, base_name) = os.path.split(file_name)
133
  (fd, new_name) = tempfile.mkstemp(suffix=".new", prefix=base_name,
134
                                    dir=dir_name)
135
  try:
136
    try:
137
      if uid != -1 or gid != -1:
138
        os.chown(new_name, uid, gid)
139
      if mode:
140
        os.chmod(new_name, mode)
141
      if callable(prewrite):
142
        prewrite(fd)
143
      if data is not None:
144
        if isinstance(data, unicode):
145
          data = data.encode()
146
        assert isinstance(data, str)
147
        to_write = len(data)
148
        offset = 0
149
        while offset < to_write:
150
          written = os.write(fd, buffer(data, offset))
151
          assert written >= 0
152
          assert written <= to_write - offset
153
          offset += written
154
        assert offset == to_write
155
      else:
156
        fn(fd)
157
      if callable(postwrite):
158
        postwrite(fd)
159
      os.fsync(fd)
160
      if atime is not None and mtime is not None:
161
        os.utime(new_name, (atime, mtime))
162
    finally:
163
      # Close file unless the file descriptor should be returned
164
      if close:
165
        os.close(fd)
166
      else:
167
        result = fd
168

    
169
    # Rename file to destination name
170
    if not dry_run:
171
      os.rename(new_name, file_name)
172
      # Successful, no need to remove anymore
173
      do_remove = False
174
  finally:
175
    if do_remove:
176
      RemoveFile(new_name)
177

    
178
  return result
179

    
180

    
181
def GetFileID(path=None, fd=None):
182
  """Returns the file 'id', i.e. the dev/inode and mtime information.
183

184
  Either the path to the file or the fd must be given.
185

186
  @param path: the file path
187
  @param fd: a file descriptor
188
  @return: a tuple of (device number, inode number, mtime)
189

190
  """
191
  if [path, fd].count(None) != 1:
192
    raise errors.ProgrammerError("One and only one of fd/path must be given")
193

    
194
  if fd is None:
195
    st = os.stat(path)
196
  else:
197
    st = os.fstat(fd)
198

    
199
  return (st.st_dev, st.st_ino, st.st_mtime)
200

    
201

    
202
def VerifyFileID(fi_disk, fi_ours):
203
  """Verifies that two file IDs are matching.
204

205
  Differences in the inode/device are not accepted, but and older
206
  timestamp for fi_disk is accepted.
207

208
  @param fi_disk: tuple (dev, inode, mtime) representing the actual
209
      file data
210
  @param fi_ours: tuple (dev, inode, mtime) representing the last
211
      written file data
212
  @rtype: boolean
213

214
  """
215
  (d1, i1, m1) = fi_disk
216
  (d2, i2, m2) = fi_ours
217

    
218
  return (d1, i1) == (d2, i2) and m1 <= m2
219

    
220

    
221
def SafeWriteFile(file_name, file_id, **kwargs):
222
  """Wraper over L{WriteFile} that locks the target file.
223

224
  By keeping the target file locked during WriteFile, we ensure that
225
  cooperating writers will safely serialise access to the file.
226

227
  @type file_name: str
228
  @param file_name: the target filename
229
  @type file_id: tuple
230
  @param file_id: a result from L{GetFileID}
231

232
  """
233
  fd = os.open(file_name, os.O_RDONLY | os.O_CREAT)
234
  try:
235
    filelock.LockFile(fd)
236
    if file_id is not None:
237
      disk_id = GetFileID(fd=fd)
238
      if not VerifyFileID(disk_id, file_id):
239
        raise errors.LockError("Cannot overwrite file %s, it has been modified"
240
                               " since last written" % file_name)
241
    return WriteFile(file_name, **kwargs)
242
  finally:
243
    os.close(fd)
244

    
245

    
246
def ReadOneLineFile(file_name, strict=False):
247
  """Return the first non-empty line from a file.
248

249
  @type strict: boolean
250
  @param strict: if True, abort if the file has more than one
251
      non-empty line
252

253
  """
254
  file_lines = ReadFile(file_name).splitlines()
255
  full_lines = filter(bool, file_lines)
256
  if not file_lines or not full_lines:
257
    raise errors.GenericError("No data in one-liner file %s" % file_name)
258
  elif strict and len(full_lines) > 1:
259
    raise errors.GenericError("Too many lines in one-liner file %s" %
260
                              file_name)
261
  return full_lines[0]
262

    
263

    
264
def RemoveFile(filename):
265
  """Remove a file ignoring some errors.
266

267
  Remove a file, ignoring non-existing ones or directories. Other
268
  errors are passed.
269

270
  @type filename: str
271
  @param filename: the file to be removed
272

273
  """
274
  try:
275
    os.unlink(filename)
276
  except OSError, err:
277
    if err.errno not in (errno.ENOENT, errno.EISDIR):
278
      raise
279

    
280

    
281
def RemoveDir(dirname):
282
  """Remove an empty directory.
283

284
  Remove a directory, ignoring non-existing ones.
285
  Other errors are passed. This includes the case,
286
  where the directory is not empty, so it can't be removed.
287

288
  @type dirname: str
289
  @param dirname: the empty directory to be removed
290

291
  """
292
  try:
293
    os.rmdir(dirname)
294
  except OSError, err:
295
    if err.errno != errno.ENOENT:
296
      raise
297

    
298

    
299
def RenameFile(old, new, mkdir=False, mkdir_mode=0750, dir_uid=None,
300
               dir_gid=None):
301
  """Renames a file.
302

303
  @type old: string
304
  @param old: Original path
305
  @type new: string
306
  @param new: New path
307
  @type mkdir: bool
308
  @param mkdir: Whether to create target directory if it doesn't exist
309
  @type mkdir_mode: int
310
  @param mkdir_mode: Mode for newly created directories
311
  @type dir_uid: int
312
  @param dir_uid: The uid for the (if fresh created) dir
313
  @type dir_gid: int
314
  @param dir_gid: The gid for the (if fresh created) dir
315

316
  """
317
  try:
318
    return os.rename(old, new)
319
  except OSError, err:
320
    # In at least one use case of this function, the job queue, directory
321
    # creation is very rare. Checking for the directory before renaming is not
322
    # as efficient.
323
    if mkdir and err.errno == errno.ENOENT:
324
      # Create directory and try again
325
      dir_path = os.path.dirname(new)
326
      Makedirs(dir_path, mode=mkdir_mode)
327
      if not (dir_uid is None or dir_gid is None):
328
        os.chown(dir_path, dir_uid, dir_gid)
329

    
330
      return os.rename(old, new)
331

    
332
    raise
333

    
334

    
335
def EnforcePermission(path, mode, uid=-1, gid=-1, must_exist=True,
336
                      _chmod_fn=os.chmod, _chown_fn=os.chown, _stat_fn=os.stat):
337
  """Enforces that given path has given permissions.
338

339
  @param path: The path to the file
340
  @param mode: The mode of the file
341
  @param uid: The uid of the owner of this file
342
  @param gid: The gid of the owner of this file
343
  @param must_exist: Specifies if non-existance of path will be an error
344
  @param _chmod_fn: chmod function to use (unittest only)
345
  @param _chown_fn: chown function to use (unittest only)
346

347
  """
348
  logging.debug("Checking %s", path)
349
  try:
350
    st = _stat_fn(path)
351

    
352
    fmode = stat.S_IMODE(st[stat.ST_MODE])
353
    if fmode != mode:
354
      logging.debug("Changing mode of %s from %#o to %#o", path, fmode, mode)
355
      _chmod_fn(path, mode)
356

    
357
    if max(uid, gid) > -1:
358
      fuid = st[stat.ST_UID]
359
      fgid = st[stat.ST_GID]
360
      if fuid != uid or fgid != gid:
361
        logging.debug("Changing owner of %s from UID %s/GID %s to"
362
                      " UID %s/GID %s", path, fuid, fgid, uid, gid)
363
        _chown_fn(path, uid, gid)
364
  except EnvironmentError, err:
365
    if err.errno == errno.ENOENT:
366
      if must_exist:
367
        raise errors.GenericError("Path %s should exist, but does not" % path)
368
    else:
369
      raise errors.GenericError("Error while changing permissions on %s: %s" %
370
                                (path, err))
371

    
372

    
373
def MakeDirWithPerm(path, mode, uid, gid, _lstat_fn=os.lstat,
374
                    _mkdir_fn=os.mkdir, _perm_fn=EnforcePermission):
375
  """Enforces that given path is a dir and has given mode, uid and gid set.
376

377
  @param path: The path to the file
378
  @param mode: The mode of the file
379
  @param uid: The uid of the owner of this file
380
  @param gid: The gid of the owner of this file
381
  @param _lstat_fn: Stat function to use (unittest only)
382
  @param _mkdir_fn: mkdir function to use (unittest only)
383
  @param _perm_fn: permission setter function to use (unittest only)
384

385
  """
386
  logging.debug("Checking directory %s", path)
387
  try:
388
    # We don't want to follow symlinks
389
    st = _lstat_fn(path)
390
  except EnvironmentError, err:
391
    if err.errno != errno.ENOENT:
392
      raise errors.GenericError("stat(2) on %s failed: %s" % (path, err))
393
    _mkdir_fn(path)
394
  else:
395
    if not stat.S_ISDIR(st[stat.ST_MODE]):
396
      raise errors.GenericError(("Path %s is expected to be a directory, but "
397
                                 "isn't") % path)
398

    
399
  _perm_fn(path, mode, uid=uid, gid=gid)
400

    
401

    
402
def Makedirs(path, mode=0750):
403
  """Super-mkdir; create a leaf directory and all intermediate ones.
404

405
  This is a wrapper around C{os.makedirs} adding error handling not implemented
406
  before Python 2.5.
407

408
  """
409
  try:
410
    os.makedirs(path, mode)
411
  except OSError, err:
412
    # Ignore EEXIST. This is only handled in os.makedirs as included in
413
    # Python 2.5 and above.
414
    if err.errno != errno.EEXIST or not os.path.exists(path):
415
      raise
416

    
417

    
418
def TimestampForFilename():
419
  """Returns the current time formatted for filenames.
420

421
  The format doesn't contain colons as some shells and applications treat them
422
  as separators. Uses the local timezone.
423

424
  """
425
  return time.strftime("%Y-%m-%d_%H_%M_%S")
426

    
427

    
428
def CreateBackup(file_name):
429
  """Creates a backup of a file.
430

431
  @type file_name: str
432
  @param file_name: file to be backed up
433
  @rtype: str
434
  @return: the path to the newly created backup
435
  @raise errors.ProgrammerError: for invalid file names
436

437
  """
438
  if not os.path.isfile(file_name):
439
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
440
                                file_name)
441

    
442
  prefix = ("%s.backup-%s." %
443
            (os.path.basename(file_name), TimestampForFilename()))
444
  dir_name = os.path.dirname(file_name)
445

    
446
  fsrc = open(file_name, "rb")
447
  try:
448
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
449
    fdst = os.fdopen(fd, "wb")
450
    try:
451
      logging.debug("Backing up %s at %s", file_name, backup_name)
452
      shutil.copyfileobj(fsrc, fdst)
453
    finally:
454
      fdst.close()
455
  finally:
456
    fsrc.close()
457

    
458
  return backup_name
459

    
460

    
461
def ListVisibleFiles(path):
462
  """Returns a list of visible files in a directory.
463

464
  @type path: str
465
  @param path: the directory to enumerate
466
  @rtype: list
467
  @return: the list of all files not starting with a dot
468
  @raise ProgrammerError: if L{path} is not an absolue and normalized path
469

470
  """
471
  if not IsNormAbsPath(path):
472
    raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
473
                                 " absolute/normalized: '%s'" % path)
474
  files = [i for i in os.listdir(path) if not i.startswith(".")]
475
  return files
476

    
477

    
478
def EnsureDirs(dirs):
479
  """Make required directories, if they don't exist.
480

481
  @param dirs: list of tuples (dir_name, dir_mode)
482
  @type dirs: list of (string, integer)
483

484
  """
485
  for dir_name, dir_mode in dirs:
486
    try:
487
      os.mkdir(dir_name, dir_mode)
488
    except EnvironmentError, err:
489
      if err.errno != errno.EEXIST:
490
        raise errors.GenericError("Cannot create needed directory"
491
                                  " '%s': %s" % (dir_name, err))
492
    try:
493
      os.chmod(dir_name, dir_mode)
494
    except EnvironmentError, err:
495
      raise errors.GenericError("Cannot change directory permissions on"
496
                                " '%s': %s" % (dir_name, err))
497
    if not os.path.isdir(dir_name):
498
      raise errors.GenericError("%s is not a directory" % dir_name)
499

    
500

    
501
def FindFile(name, search_path, test=os.path.exists):
502
  """Look for a filesystem object in a given path.
503

504
  This is an abstract method to search for filesystem object (files,
505
  dirs) under a given search path.
506

507
  @type name: str
508
  @param name: the name to look for
509
  @type search_path: str
510
  @param search_path: location to start at
511
  @type test: callable
512
  @param test: a function taking one argument that should return True
513
      if the a given object is valid; the default value is
514
      os.path.exists, causing only existing files to be returned
515
  @rtype: str or None
516
  @return: full path to the object if found, None otherwise
517

518
  """
519
  # validate the filename mask
520
  if constants.EXT_PLUGIN_MASK.match(name) is None:
521
    logging.critical("Invalid value passed for external script name: '%s'",
522
                     name)
523
    return None
524

    
525
  for dir_name in search_path:
526
    # FIXME: investigate switch to PathJoin
527
    item_name = os.path.sep.join([dir_name, name])
528
    # check the user test and that we're indeed resolving to the given
529
    # basename
530
    if test(item_name) and os.path.basename(item_name) == name:
531
      return item_name
532
  return None
533

    
534

    
535
def IsNormAbsPath(path):
536
  """Check whether a path is absolute and also normalized
537

538
  This avoids things like /dir/../../other/path to be valid.
539

540
  """
541
  return os.path.normpath(path) == path and os.path.isabs(path)
542

    
543

    
544
def IsBelowDir(root, other_path):
545
  """Check whether a path is below a root dir.
546

547
  This works around the nasty byte-byte comparisation of commonprefix.
548

549
  """
550
  if not (os.path.isabs(root) and os.path.isabs(other_path)):
551
    raise ValueError("Provided paths '%s' and '%s' are not absolute" %
552
                     (root, other_path))
553
  prepared_root = "%s%s" % (os.path.normpath(root), os.sep)
554
  return os.path.commonprefix([prepared_root,
555
                               os.path.normpath(other_path)]) == prepared_root
556

    
557

    
558
def PathJoin(*args):
559
  """Safe-join a list of path components.
560

561
  Requirements:
562
      - the first argument must be an absolute path
563
      - no component in the path must have backtracking (e.g. /../),
564
        since we check for normalization at the end
565

566
  @param args: the path components to be joined
567
  @raise ValueError: for invalid paths
568

569
  """
570
  # ensure we're having at least one path passed in
571
  assert args
572
  # ensure the first component is an absolute and normalized path name
573
  root = args[0]
574
  if not IsNormAbsPath(root):
575
    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
576
  result = os.path.join(*args)
577
  # ensure that the whole path is normalized
578
  if not IsNormAbsPath(result):
579
    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
580
  # check that we're still under the original prefix
581
  if not IsBelowDir(root, result):
582
    raise ValueError("Error: path joining resulted in different prefix"
583
                     " (%s != %s)" % (result, root))
584
  return result
585

    
586

    
587
def TailFile(fname, lines=20):
588
  """Return the last lines from a file.
589

590
  @note: this function will only read and parse the last 4KB of
591
      the file; if the lines are very long, it could be that less
592
      than the requested number of lines are returned
593

594
  @param fname: the file name
595
  @type lines: int
596
  @param lines: the (maximum) number of lines to return
597

598
  """
599
  fd = open(fname, "r")
600
  try:
601
    fd.seek(0, 2)
602
    pos = fd.tell()
603
    pos = max(0, pos - 4096)
604
    fd.seek(pos, 0)
605
    raw_data = fd.read()
606
  finally:
607
    fd.close()
608

    
609
  rows = raw_data.splitlines()
610
  return rows[-lines:]
611

    
612

    
613
def BytesToMebibyte(value):
614
  """Converts bytes to mebibytes.
615

616
  @type value: int
617
  @param value: Value in bytes
618
  @rtype: int
619
  @return: Value in mebibytes
620

621
  """
622
  return int(round(value / (1024.0 * 1024.0), 0))
623

    
624

    
625
def CalculateDirectorySize(path):
626
  """Calculates the size of a directory recursively.
627

628
  @type path: string
629
  @param path: Path to directory
630
  @rtype: int
631
  @return: Size in mebibytes
632

633
  """
634
  size = 0
635

    
636
  for (curpath, _, files) in os.walk(path):
637
    for filename in files:
638
      st = os.lstat(PathJoin(curpath, filename))
639
      size += st.st_size
640

    
641
  return BytesToMebibyte(size)
642

    
643

    
644
def GetFilesystemStats(path):
645
  """Returns the total and free space on a filesystem.
646

647
  @type path: string
648
  @param path: Path on filesystem to be examined
649
  @rtype: int
650
  @return: tuple of (Total space, Free space) in mebibytes
651

652
  """
653
  st = os.statvfs(path)
654

    
655
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
656
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
657
  return (tsize, fsize)
658

    
659

    
660
def ReadPidFile(pidfile):
661
  """Read a pid from a file.
662

663
  @type  pidfile: string
664
  @param pidfile: path to the file containing the pid
665
  @rtype: int
666
  @return: The process id, if the file exists and contains a valid PID,
667
           otherwise 0
668

669
  """
670
  try:
671
    raw_data = ReadOneLineFile(pidfile)
672
  except EnvironmentError, err:
673
    if err.errno != errno.ENOENT:
674
      logging.exception("Can't read pid file")
675
    return 0
676

    
677
  try:
678
    pid = int(raw_data)
679
  except (TypeError, ValueError), err:
680
    logging.info("Can't parse pid file contents", exc_info=True)
681
    return 0
682

    
683
  return pid
684

    
685

    
686
def ReadLockedPidFile(path):
687
  """Reads a locked PID file.
688

689
  This can be used together with L{utils.process.StartDaemon}.
690

691
  @type path: string
692
  @param path: Path to PID file
693
  @return: PID as integer or, if file was unlocked or couldn't be opened, None
694

695
  """
696
  try:
697
    fd = os.open(path, os.O_RDONLY)
698
  except EnvironmentError, err:
699
    if err.errno == errno.ENOENT:
700
      # PID file doesn't exist
701
      return None
702
    raise
703

    
704
  try:
705
    try:
706
      # Try to acquire lock
707
      filelock.LockFile(fd)
708
    except errors.LockError:
709
      # Couldn't lock, daemon is running
710
      return int(os.read(fd, 100))
711
  finally:
712
    os.close(fd)
713

    
714
  return None
715

    
716

    
717
def AddAuthorizedKey(file_obj, key):
718
  """Adds an SSH public key to an authorized_keys file.
719

720
  @type file_obj: str or file handle
721
  @param file_obj: path to authorized_keys file
722
  @type key: str
723
  @param key: string containing key
724

725
  """
726
  key_fields = key.split()
727

    
728
  if isinstance(file_obj, basestring):
729
    f = open(file_obj, "a+")
730
  else:
731
    f = file_obj
732

    
733
  try:
734
    nl = True
735
    for line in f:
736
      # Ignore whitespace changes
737
      if line.split() == key_fields:
738
        break
739
      nl = line.endswith("\n")
740
    else:
741
      if not nl:
742
        f.write("\n")
743
      f.write(key.rstrip("\r\n"))
744
      f.write("\n")
745
      f.flush()
746
  finally:
747
    f.close()
748

    
749

    
750
def RemoveAuthorizedKey(file_name, key):
751
  """Removes an SSH public key from an authorized_keys file.
752

753
  @type file_name: str
754
  @param file_name: path to authorized_keys file
755
  @type key: str
756
  @param key: string containing key
757

758
  """
759
  key_fields = key.split()
760

    
761
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
762
  try:
763
    out = os.fdopen(fd, "w")
764
    try:
765
      f = open(file_name, "r")
766
      try:
767
        for line in f:
768
          # Ignore whitespace changes while comparing lines
769
          if line.split() != key_fields:
770
            out.write(line)
771

    
772
        out.flush()
773
        os.rename(tmpname, file_name)
774
      finally:
775
        f.close()
776
    finally:
777
      out.close()
778
  except:
779
    RemoveFile(tmpname)
780
    raise
781

    
782

    
783
def DaemonPidFileName(name):
784
  """Compute a ganeti pid file absolute path
785

786
  @type name: str
787
  @param name: the daemon name
788
  @rtype: str
789
  @return: the full path to the pidfile corresponding to the given
790
      daemon name
791

792
  """
793
  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
794

    
795

    
796
def WritePidFile(pidfile):
797
  """Write the current process pidfile.
798

799
  @type pidfile: string
800
  @param pidfile: the path to the file to be written
801
  @raise errors.LockError: if the pid file already exists and
802
      points to a live process
803
  @rtype: int
804
  @return: the file descriptor of the lock file; do not close this unless
805
      you want to unlock the pid file
806

807
  """
808
  # We don't rename nor truncate the file to not drop locks under
809
  # existing processes
810
  fd_pidfile = os.open(pidfile, os.O_WRONLY | os.O_CREAT, 0600)
811

    
812
  # Lock the PID file (and fail if not possible to do so). Any code
813
  # wanting to send a signal to the daemon should try to lock the PID
814
  # file before reading it. If acquiring the lock succeeds, the daemon is
815
  # no longer running and the signal should not be sent.
816
  filelock.LockFile(fd_pidfile)
817

    
818
  os.write(fd_pidfile, "%d\n" % os.getpid())
819

    
820
  return fd_pidfile
821

    
822

    
823
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
824
  """Reads the watcher pause file.
825

826
  @type filename: string
827
  @param filename: Path to watcher pause file
828
  @type now: None, float or int
829
  @param now: Current time as Unix timestamp
830
  @type remove_after: int
831
  @param remove_after: Remove watcher pause file after specified amount of
832
    seconds past the pause end time
833

834
  """
835
  if now is None:
836
    now = time.time()
837

    
838
  try:
839
    value = ReadFile(filename)
840
  except IOError, err:
841
    if err.errno != errno.ENOENT:
842
      raise
843
    value = None
844

    
845
  if value is not None:
846
    try:
847
      value = int(value)
848
    except ValueError:
849
      logging.warning(("Watcher pause file (%s) contains invalid value,"
850
                       " removing it"), filename)
851
      RemoveFile(filename)
852
      value = None
853

    
854
    if value is not None:
855
      # Remove file if it's outdated
856
      if now > (value + remove_after):
857
        RemoveFile(filename)
858
        value = None
859

    
860
      elif now > value:
861
        value = None
862

    
863
  return value
864

    
865

    
866
def NewUUID():
867
  """Returns a random UUID.
868

869
  @note: This is a Linux-specific method as it uses the /proc
870
      filesystem.
871
  @rtype: str
872

873
  """
874
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")