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