1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
39 _RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
40
41
42
43 _LOST_AND_FOUND = "lost+found"
44
45
46 KP_NEVER = 0
47 KP_ALWAYS = 1
48 KP_IF_EXISTS = 2
49
50 KEEP_PERMS_VALUES = [
51 KP_NEVER,
52 KP_ALWAYS,
53 KP_IF_EXISTS,
54 ]
55
56
58 """Format an EnvironmentError exception.
59
60 If the L{err} argument has an errno attribute, it will be looked up
61 and converted into a textual C{E...} description. Otherwise the
62 string representation of the error will be returned.
63
64 @type err: L{EnvironmentError}
65 @param err: the exception to format
66
67 """
68 if hasattr(err, "errno"):
69 detail = errno.errorcode[err.errno]
70 else:
71 detail = str(err)
72 return detail
73
74
76 """Helper to store file handle's C{fstat}.
77
78 Useful in combination with L{ReadFile}'s C{preread} parameter.
79
80 """
82 """Initializes this class.
83
84 """
85 self.st = None
86
88 """Calls C{fstat} on file handle.
89
90 """
91 self.st = os.fstat(fh.fileno())
92
93
94 -def ReadFile(file_name, size=-1, preread=None):
95 """Reads a file.
96
97 @type size: int
98 @param size: Read at most size bytes (if negative, entire file)
99 @type preread: callable receiving file handle as single parameter
100 @param preread: Function called before file is read
101 @rtype: str
102 @return: the (possibly partial) content of the file
103
104 """
105 f = open(file_name, "r")
106 try:
107 if preread:
108 preread(f)
109
110 return f.read(size)
111 finally:
112 f.close()
113
114
115 -def WriteFile(file_name, fn=None, data=None,
116 mode=None, uid=-1, gid=-1,
117 atime=None, mtime=None, close=True,
118 dry_run=False, backup=False,
119 prewrite=None, postwrite=None, keep_perms=KP_NEVER):
120 """(Over)write a file atomically.
121
122 The file_name and either fn (a function taking one argument, the
123 file descriptor, and which should write the data to it) or data (the
124 contents of the file) must be passed. The other arguments are
125 optional and allow setting the file mode, owner and group, and the
126 mtime/atime of the file.
127
128 If the function doesn't raise an exception, it has succeeded and the
129 target file has the new contents. If the function has raised an
130 exception, an existing target file should be unmodified and the
131 temporary file should be removed.
132
133 @type file_name: str
134 @param file_name: the target filename
135 @type fn: callable
136 @param fn: content writing function, called with
137 file descriptor as parameter
138 @type data: str
139 @param data: contents of the file
140 @type mode: int
141 @param mode: file mode
142 @type uid: int
143 @param uid: the owner of the file
144 @type gid: int
145 @param gid: the group of the file
146 @type atime: int
147 @param atime: a custom access time to be set on the file
148 @type mtime: int
149 @param mtime: a custom modification time to be set on the file
150 @type close: boolean
151 @param close: whether to close file after writing it
152 @type prewrite: callable
153 @param prewrite: function to be called before writing content
154 @type postwrite: callable
155 @param postwrite: function to be called after writing content
156 @type keep_perms: members of L{KEEP_PERMS_VALUES}
157 @param keep_perms: if L{KP_NEVER} (default), owner, group, and mode are
158 taken from the other parameters; if L{KP_ALWAYS}, owner, group, and
159 mode are copied from the existing file; if L{KP_IF_EXISTS}, owner,
160 group, and mode are taken from the file, and if the file doesn't
161 exist, they are taken from the other parameters. It is an error to
162 pass L{KP_ALWAYS} when the file doesn't exist or when C{uid}, C{gid},
163 or C{mode} are set to non-default values.
164
165 @rtype: None or int
166 @return: None if the 'close' parameter evaluates to True,
167 otherwise the file descriptor
168
169 @raise errors.ProgrammerError: if any of the arguments are not valid
170
171 """
172 if not os.path.isabs(file_name):
173 raise errors.ProgrammerError("Path passed to WriteFile is not"
174 " absolute: '%s'" % file_name)
175
176 if [fn, data].count(None) != 1:
177 raise errors.ProgrammerError("fn or data required")
178
179 if [atime, mtime].count(None) == 1:
180 raise errors.ProgrammerError("Both atime and mtime must be either"
181 " set or None")
182
183 if not keep_perms in KEEP_PERMS_VALUES:
184 raise errors.ProgrammerError("Invalid value for keep_perms: %s" %
185 keep_perms)
186 if keep_perms == KP_ALWAYS and (uid != -1 or gid != -1 or mode is not None):
187 raise errors.ProgrammerError("When keep_perms==KP_ALWAYS, 'uid', 'gid',"
188 " and 'mode' cannot be set")
189
190 if backup and not dry_run and os.path.isfile(file_name):
191 CreateBackup(file_name)
192
193 if keep_perms == KP_ALWAYS or keep_perms == KP_IF_EXISTS:
194
195 try:
196 file_stat = os.stat(file_name)
197 mode = stat.S_IMODE(file_stat.st_mode)
198 uid = file_stat.st_uid
199 gid = file_stat.st_gid
200 except OSError:
201 if keep_perms == KP_ALWAYS:
202 raise
203
204
205
206 do_remove = True
207
208
209 result = None
210
211 (dir_name, base_name) = os.path.split(file_name)
212 (fd, new_name) = tempfile.mkstemp(suffix=".new", prefix=base_name,
213 dir=dir_name)
214 try:
215 try:
216 if uid != -1 or gid != -1:
217 os.chown(new_name, uid, gid)
218 if mode:
219 os.chmod(new_name, mode)
220 if callable(prewrite):
221 prewrite(fd)
222 if data is not None:
223 if isinstance(data, unicode):
224 data = data.encode()
225 assert isinstance(data, str)
226 to_write = len(data)
227 offset = 0
228 while offset < to_write:
229 written = os.write(fd, buffer(data, offset))
230 assert written >= 0
231 assert written <= to_write - offset
232 offset += written
233 assert offset == to_write
234 else:
235 fn(fd)
236 if callable(postwrite):
237 postwrite(fd)
238 os.fsync(fd)
239 if atime is not None and mtime is not None:
240 os.utime(new_name, (atime, mtime))
241 finally:
242
243 if close:
244 os.close(fd)
245 else:
246 result = fd
247
248
249 if not dry_run:
250 os.rename(new_name, file_name)
251
252 do_remove = False
253 finally:
254 if do_remove:
255 RemoveFile(new_name)
256
257 return result
258
259
261 """Returns the file 'id', i.e. the dev/inode and mtime information.
262
263 Either the path to the file or the fd must be given.
264
265 @param path: the file path
266 @param fd: a file descriptor
267 @return: a tuple of (device number, inode number, mtime)
268
269 """
270 if [path, fd].count(None) != 1:
271 raise errors.ProgrammerError("One and only one of fd/path must be given")
272
273 if fd is None:
274 st = os.stat(path)
275 else:
276 st = os.fstat(fd)
277
278 return (st.st_dev, st.st_ino, st.st_mtime)
279
280
282 """Verifies that two file IDs are matching.
283
284 Differences in the inode/device are not accepted, but and older
285 timestamp for fi_disk is accepted.
286
287 @param fi_disk: tuple (dev, inode, mtime) representing the actual
288 file data
289 @param fi_ours: tuple (dev, inode, mtime) representing the last
290 written file data
291 @rtype: boolean
292
293 """
294 (d1, i1, m1) = fi_disk
295 (d2, i2, m2) = fi_ours
296
297 return (d1, i1) == (d2, i2) and m1 <= m2
298
299
301 """Wraper over L{WriteFile} that locks the target file.
302
303 By keeping the target file locked during WriteFile, we ensure that
304 cooperating writers will safely serialise access to the file.
305
306 @type file_name: str
307 @param file_name: the target filename
308 @type file_id: tuple
309 @param file_id: a result from L{GetFileID}
310
311 """
312 fd = os.open(file_name, os.O_RDONLY | os.O_CREAT)
313 try:
314 filelock.LockFile(fd)
315 if file_id is not None:
316 disk_id = GetFileID(fd=fd)
317 if not VerifyFileID(disk_id, file_id):
318 raise errors.LockError("Cannot overwrite file %s, it has been modified"
319 " since last written" % file_name)
320 return WriteFile(file_name, **kwargs)
321 finally:
322 os.close(fd)
323
324
326 """Return the first non-empty line from a file.
327
328 @type strict: boolean
329 @param strict: if True, abort if the file has more than one
330 non-empty line
331
332 """
333 file_lines = ReadFile(file_name).splitlines()
334 full_lines = filter(bool, file_lines)
335 if not file_lines or not full_lines:
336 raise errors.GenericError("No data in one-liner file %s" % file_name)
337 elif strict and len(full_lines) > 1:
338 raise errors.GenericError("Too many lines in one-liner file %s" %
339 file_name)
340 return full_lines[0]
341
342
344 """Remove a file ignoring some errors.
345
346 Remove a file, ignoring non-existing ones or directories. Other
347 errors are passed.
348
349 @type filename: str
350 @param filename: the file to be removed
351
352 """
353 try:
354 os.unlink(filename)
355 except OSError, err:
356 if err.errno not in (errno.ENOENT, errno.EISDIR):
357 raise
358
359
361 """Remove an empty directory.
362
363 Remove a directory, ignoring non-existing ones.
364 Other errors are passed. This includes the case,
365 where the directory is not empty, so it can't be removed.
366
367 @type dirname: str
368 @param dirname: the empty directory to be removed
369
370 """
371 try:
372 os.rmdir(dirname)
373 except OSError, err:
374 if err.errno != errno.ENOENT:
375 raise
376
377
378 -def RenameFile(old, new, mkdir=False, mkdir_mode=0750, dir_uid=None,
379 dir_gid=None):
380 """Renames a file.
381
382 This just creates the very least directory if it does not exist and C{mkdir}
383 is set to true.
384
385 @type old: string
386 @param old: Original path
387 @type new: string
388 @param new: New path
389 @type mkdir: bool
390 @param mkdir: Whether to create target directory if it doesn't exist
391 @type mkdir_mode: int
392 @param mkdir_mode: Mode for newly created directories
393 @type dir_uid: int
394 @param dir_uid: The uid for the (if fresh created) dir
395 @type dir_gid: int
396 @param dir_gid: The gid for the (if fresh created) dir
397
398 """
399 try:
400 return os.rename(old, new)
401 except OSError, err:
402
403
404
405 if mkdir and err.errno == errno.ENOENT:
406
407 dir_path = os.path.dirname(new)
408 MakeDirWithPerm(dir_path, mkdir_mode, dir_uid, dir_gid)
409
410 return os.rename(old, new)
411
412 raise
413
414
415 -def EnforcePermission(path, mode, uid=None, gid=None, must_exist=True,
416 _chmod_fn=os.chmod, _chown_fn=os.chown, _stat_fn=os.stat):
417 """Enforces that given path has given permissions.
418
419 @param path: The path to the file
420 @param mode: The mode of the file
421 @param uid: The uid of the owner of this file
422 @param gid: The gid of the owner of this file
423 @param must_exist: Specifies if non-existance of path will be an error
424 @param _chmod_fn: chmod function to use (unittest only)
425 @param _chown_fn: chown function to use (unittest only)
426
427 """
428 logging.debug("Checking %s", path)
429
430
431
432 if uid is None:
433 uid = -1
434 if gid is None:
435 gid = -1
436
437 try:
438 st = _stat_fn(path)
439
440 fmode = stat.S_IMODE(st[stat.ST_MODE])
441 if fmode != mode:
442 logging.debug("Changing mode of %s from %#o to %#o", path, fmode, mode)
443 _chmod_fn(path, mode)
444
445 if max(uid, gid) > -1:
446 fuid = st[stat.ST_UID]
447 fgid = st[stat.ST_GID]
448 if fuid != uid or fgid != gid:
449 logging.debug("Changing owner of %s from UID %s/GID %s to"
450 " UID %s/GID %s", path, fuid, fgid, uid, gid)
451 _chown_fn(path, uid, gid)
452 except EnvironmentError, err:
453 if err.errno == errno.ENOENT:
454 if must_exist:
455 raise errors.GenericError("Path %s should exist, but does not" % path)
456 else:
457 raise errors.GenericError("Error while changing permissions on %s: %s" %
458 (path, err))
459
460
463 """Enforces that given path is a dir and has given mode, uid and gid set.
464
465 @param path: The path to the file
466 @param mode: The mode of the file
467 @param uid: The uid of the owner of this file
468 @param gid: The gid of the owner of this file
469 @param _lstat_fn: Stat function to use (unittest only)
470 @param _mkdir_fn: mkdir function to use (unittest only)
471 @param _perm_fn: permission setter function to use (unittest only)
472
473 """
474 logging.debug("Checking directory %s", path)
475 try:
476
477 st = _lstat_fn(path)
478 except EnvironmentError, err:
479 if err.errno != errno.ENOENT:
480 raise errors.GenericError("stat(2) on %s failed: %s" % (path, err))
481 _mkdir_fn(path)
482 else:
483 if not stat.S_ISDIR(st[stat.ST_MODE]):
484 raise errors.GenericError(("Path %s is expected to be a directory, but "
485 "isn't") % path)
486
487 _perm_fn(path, mode, uid=uid, gid=gid)
488
489
491 """Super-mkdir; create a leaf directory and all intermediate ones.
492
493 This is a wrapper around C{os.makedirs} adding error handling not implemented
494 before Python 2.5.
495
496 """
497 try:
498 os.makedirs(path, mode)
499 except OSError, err:
500
501
502 if err.errno != errno.EEXIST or not os.path.exists(path):
503 raise
504
505
507 """Returns the current time formatted for filenames.
508
509 The format doesn't contain colons as some shells and applications treat them
510 as separators. Uses the local timezone.
511
512 """
513 return time.strftime("%Y-%m-%d_%H_%M_%S")
514
515
517 """Creates a backup of a file.
518
519 @type file_name: str
520 @param file_name: file to be backed up
521 @rtype: str
522 @return: the path to the newly created backup
523 @raise errors.ProgrammerError: for invalid file names
524
525 """
526 if not os.path.isfile(file_name):
527 raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
528 file_name)
529
530 prefix = ("%s.backup-%s." %
531 (os.path.basename(file_name), TimestampForFilename()))
532 dir_name = os.path.dirname(file_name)
533
534 fsrc = open(file_name, "rb")
535 try:
536 (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
537 fdst = os.fdopen(fd, "wb")
538 try:
539 logging.debug("Backing up %s at %s", file_name, backup_name)
540 shutil.copyfileobj(fsrc, fdst)
541 finally:
542 fdst.close()
543 finally:
544 fsrc.close()
545
546 return backup_name
547
548
550 """Returns a list of visible files in a directory.
551
552 @type path: str
553 @param path: the directory to enumerate
554 @rtype: list
555 @return: the list of all files not starting with a dot
556 @raise ProgrammerError: if L{path} is not an absolue and normalized path
557
558 """
559 if not IsNormAbsPath(path):
560 raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
561 " absolute/normalized: '%s'" % path)
562
563 mountpoint = _is_mountpoint(path)
564
565 def fn(name):
566 """File name filter.
567
568 Ignores files starting with a dot (".") as by Unix convention they're
569 considered hidden. The "lost+found" directory found at the root of some
570 filesystems is also hidden.
571
572 """
573 return not (name.startswith(".") or
574 (mountpoint and name == _LOST_AND_FOUND and
575 os.path.isdir(os.path.join(path, name))))
576
577 return filter(fn, os.listdir(path))
578
579
581 """Make required directories, if they don't exist.
582
583 @param dirs: list of tuples (dir_name, dir_mode)
584 @type dirs: list of (string, integer)
585
586 """
587 for dir_name, dir_mode in dirs:
588 try:
589 os.mkdir(dir_name, dir_mode)
590 except EnvironmentError, err:
591 if err.errno != errno.EEXIST:
592 raise errors.GenericError("Cannot create needed directory"
593 " '%s': %s" % (dir_name, err))
594 try:
595 os.chmod(dir_name, dir_mode)
596 except EnvironmentError, err:
597 raise errors.GenericError("Cannot change directory permissions on"
598 " '%s': %s" % (dir_name, err))
599 if not os.path.isdir(dir_name):
600 raise errors.GenericError("%s is not a directory" % dir_name)
601
602
603 -def FindFile(name, search_path, test=os.path.exists):
604 """Look for a filesystem object in a given path.
605
606 This is an abstract method to search for filesystem object (files,
607 dirs) under a given search path.
608
609 @type name: str
610 @param name: the name to look for
611 @type search_path: str
612 @param search_path: location to start at
613 @type test: callable
614 @param test: a function taking one argument that should return True
615 if the a given object is valid; the default value is
616 os.path.exists, causing only existing files to be returned
617 @rtype: str or None
618 @return: full path to the object if found, None otherwise
619
620 """
621
622 if constants.EXT_PLUGIN_MASK.match(name) is None:
623 logging.critical("Invalid value passed for external script name: '%s'",
624 name)
625 return None
626
627 for dir_name in search_path:
628
629 item_name = os.path.sep.join([dir_name, name])
630
631
632 if test(item_name) and os.path.basename(item_name) == name:
633 return item_name
634 return None
635
636
638 """Check whether a path is absolute and also normalized
639
640 This avoids things like /dir/../../other/path to be valid.
641
642 """
643 return os.path.normpath(path) == path and os.path.isabs(path)
644
645
647 """Check whether a path is below a root dir.
648
649 This works around the nasty byte-byte comparisation of commonprefix.
650
651 """
652 if not (os.path.isabs(root) and os.path.isabs(other_path)):
653 raise ValueError("Provided paths '%s' and '%s' are not absolute" %
654 (root, other_path))
655 prepared_root = "%s%s" % (os.path.normpath(root), os.sep)
656 return os.path.commonprefix([prepared_root,
657 os.path.normpath(other_path)]) == prepared_root
658
659
661 """Safe-join a list of path components.
662
663 Requirements:
664 - the first argument must be an absolute path
665 - no component in the path must have backtracking (e.g. /../),
666 since we check for normalization at the end
667
668 @param args: the path components to be joined
669 @raise ValueError: for invalid paths
670
671 """
672
673 assert args
674
675 root = args[0]
676 if not IsNormAbsPath(root):
677 raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
678 result = os.path.join(*args)
679
680 if not IsNormAbsPath(result):
681 raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
682
683 if not IsBelowDir(root, result):
684 raise ValueError("Error: path joining resulted in different prefix"
685 " (%s != %s)" % (result, root))
686 return result
687
688
690 """Return the last lines from a file.
691
692 @note: this function will only read and parse the last 4KB of
693 the file; if the lines are very long, it could be that less
694 than the requested number of lines are returned
695
696 @param fname: the file name
697 @type lines: int
698 @param lines: the (maximum) number of lines to return
699
700 """
701 fd = open(fname, "r")
702 try:
703 fd.seek(0, 2)
704 pos = fd.tell()
705 pos = max(0, pos - 4096)
706 fd.seek(pos, 0)
707 raw_data = fd.read()
708 finally:
709 fd.close()
710
711 rows = raw_data.splitlines()
712 return rows[-lines:]
713
714
716 """Converts bytes to mebibytes.
717
718 @type value: int
719 @param value: Value in bytes
720 @rtype: int
721 @return: Value in mebibytes
722
723 """
724 return int(round(value / (1024.0 * 1024.0), 0))
725
726
728 """Calculates the size of a directory recursively.
729
730 @type path: string
731 @param path: Path to directory
732 @rtype: int
733 @return: Size in mebibytes
734
735 """
736 size = 0
737
738 for (curpath, _, files) in os.walk(path):
739 for filename in files:
740 st = os.lstat(PathJoin(curpath, filename))
741 size += st.st_size
742
743 return BytesToMebibyte(size)
744
745
747 """Returns the total and free space on a filesystem.
748
749 @type path: string
750 @param path: Path on filesystem to be examined
751 @rtype: int
752 @return: tuple of (Total space, Free space) in mebibytes
753
754 """
755 st = os.statvfs(path)
756
757 fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
758 tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
759 return (tsize, fsize)
760
761
763 """Read a pid from a file.
764
765 @type pidfile: string
766 @param pidfile: path to the file containing the pid
767 @rtype: int
768 @return: The process id, if the file exists and contains a valid PID,
769 otherwise 0
770
771 """
772 try:
773 raw_data = ReadOneLineFile(pidfile)
774 except EnvironmentError, err:
775 if err.errno != errno.ENOENT:
776 logging.exception("Can't read pid file")
777 return 0
778
779 return _ParsePidFileContents(raw_data)
780
781
783 """Tries to extract a process ID from a PID file's content.
784
785 @type data: string
786 @rtype: int
787 @return: Zero if nothing could be read, PID otherwise
788
789 """
790 try:
791 pid = int(data)
792 except (TypeError, ValueError):
793 logging.info("Can't parse pid file contents", exc_info=True)
794 return 0
795 else:
796 return pid
797
798
800 """Reads a locked PID file.
801
802 This can be used together with L{utils.process.StartDaemon}.
803
804 @type path: string
805 @param path: Path to PID file
806 @return: PID as integer or, if file was unlocked or couldn't be opened, None
807
808 """
809 try:
810 fd = os.open(path, os.O_RDONLY)
811 except EnvironmentError, err:
812 if err.errno == errno.ENOENT:
813
814 return None
815 raise
816
817 try:
818 try:
819
820 filelock.LockFile(fd)
821 except errors.LockError:
822
823 return int(os.read(fd, 100))
824 finally:
825 os.close(fd)
826
827 return None
828
829
831 """Adds an SSH public key to an authorized_keys file.
832
833 @type file_obj: str or file handle
834 @param file_obj: path to authorized_keys file
835 @type key: str
836 @param key: string containing key
837
838 """
839 key_fields = key.split()
840
841 if isinstance(file_obj, basestring):
842 f = open(file_obj, "a+")
843 else:
844 f = file_obj
845
846 try:
847 nl = True
848 for line in f:
849
850 if line.split() == key_fields:
851 break
852 nl = line.endswith("\n")
853 else:
854 if not nl:
855 f.write("\n")
856 f.write(key.rstrip("\r\n"))
857 f.write("\n")
858 f.flush()
859 finally:
860 f.close()
861
862
864 """Removes an SSH public key from an authorized_keys file.
865
866 @type file_name: str
867 @param file_name: path to authorized_keys file
868 @type key: str
869 @param key: string containing key
870
871 """
872 key_fields = key.split()
873
874 fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
875 try:
876 out = os.fdopen(fd, "w")
877 try:
878 f = open(file_name, "r")
879 try:
880 for line in f:
881
882 if line.split() != key_fields:
883 out.write(line)
884
885 out.flush()
886 os.rename(tmpname, file_name)
887 finally:
888 f.close()
889 finally:
890 out.close()
891 except:
892 RemoveFile(tmpname)
893 raise
894
895
897 """Compute a ganeti pid file absolute path
898
899 @type name: str
900 @param name: the daemon name
901 @rtype: str
902 @return: the full path to the pidfile corresponding to the given
903 daemon name
904
905 """
906 return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
907
908
910 """Write the current process pidfile.
911
912 @type pidfile: string
913 @param pidfile: the path to the file to be written
914 @raise errors.LockError: if the pid file already exists and
915 points to a live process
916 @rtype: int
917 @return: the file descriptor of the lock file; do not close this unless
918 you want to unlock the pid file
919
920 """
921
922
923 fd_pidfile = os.open(pidfile, os.O_RDWR | os.O_CREAT, 0600)
924
925
926
927
928
929 try:
930 filelock.LockFile(fd_pidfile)
931 except errors.LockError:
932 msg = ["PID file '%s' is already locked by another process" % pidfile]
933
934 pid = _ParsePidFileContents(os.read(fd_pidfile, 100))
935 if pid > 0:
936 msg.append(", PID read from file is %s" % pid)
937 raise errors.PidFileLockError("".join(msg))
938
939 os.write(fd_pidfile, "%d\n" % os.getpid())
940
941 return fd_pidfile
942
943
945 """Reads the watcher pause file.
946
947 @type filename: string
948 @param filename: Path to watcher pause file
949 @type now: None, float or int
950 @param now: Current time as Unix timestamp
951 @type remove_after: int
952 @param remove_after: Remove watcher pause file after specified amount of
953 seconds past the pause end time
954
955 """
956 if now is None:
957 now = time.time()
958
959 try:
960 value = ReadFile(filename)
961 except IOError, err:
962 if err.errno != errno.ENOENT:
963 raise
964 value = None
965
966 if value is not None:
967 try:
968 value = int(value)
969 except ValueError:
970 logging.warning(("Watcher pause file (%s) contains invalid value,"
971 " removing it"), filename)
972 RemoveFile(filename)
973 value = None
974
975 if value is not None:
976
977 if now > (value + remove_after):
978 RemoveFile(filename)
979 value = None
980
981 elif now > value:
982 value = None
983
984 return value
985
986
988 """Returns a random UUID.
989
990 @note: This is a Linux-specific method as it uses the /proc
991 filesystem.
992 @rtype: str
993
994 """
995 return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
996
997
999 """Stores the list of files to be deleted and removes them on demand.
1000
1001 """
1002
1005
1008
1009 - def Add(self, filename):
1010 """Add file to list of files to be deleted.
1011
1012 @type filename: string
1013 @param filename: path to filename to be added
1014
1015 """
1016 self._files.append(filename)
1017
1019 """Remove file from list of files to be deleted.
1020
1021 @type filename: string
1022 @param filename: path to filename to be deleted
1023
1024 """
1025 self._files.remove(filename)
1026
1028 """Delete all files marked for deletion
1029
1030 """
1031 while self._files:
1032 RemoveFile(self._files.pop())
1033