# Copyright (C) 2009-2015 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import stat import tempfile import unittest import obnamlib class FakeFS(object): def __init__(self): self.st_atime_sec = 1 self.st_atime_nsec = 11 self.st_blocks = 2 self.st_dev = 3 self.st_gid = 4 self.st_ino = 5 self.st_mode = 6 self.st_mtime_sec = 7 self.st_mtime_nsec = 71 self.st_nlink = 8 self.st_size = 9 self.st_uid = 10 self.groupname = 'group' self.username = 'user' self.target = 'target' def lstat(self, filename): return self def readlink(self, filename): return 'target' def getpwuid(self, uid): return (self.username, None, self.st_uid, self.st_gid, None, None, None) def getgrgid(self, gid): return (self.groupname, None, self.st_gid, None) def fail_getpwuid(self, uid): raise KeyError(uid) def fail_getgrgid(self, gid): raise KeyError(gid) def llistxattr(self, filename): return [] class MetadataTests(unittest.TestCase): def test_sets_mtime_from_kwarg(self): metadata = obnamlib.Metadata(st_mtime_sec=123) self.assertEqual(metadata.st_mtime_sec, 123) def test_isdir_returns_false_for_regular_file(self): metadata = obnamlib.Metadata(st_mode=stat.S_IFREG) self.assertFalse(metadata.isdir()) def test_isdir_returns_true_for_directory(self): metadata = obnamlib.Metadata(st_mode=stat.S_IFDIR) self.assert_(metadata.isdir()) def test_isdir_returns_false_when_st_mode_is_not_set(self): metadata = obnamlib.Metadata() self.assertFalse(metadata.isdir()) def test_islink_returns_false_for_regular_file(self): metadata = obnamlib.Metadata(st_mode=stat.S_IFREG) self.assertFalse(metadata.islink()) def test_islink_returns_true_for_symlink(self): metadata = obnamlib.Metadata(st_mode=stat.S_IFLNK) self.assert_(metadata.islink()) def test_islink_returns_false_when_st_mode_is_not_set(self): metadata = obnamlib.Metadata() self.assertFalse(metadata.islink()) def test_isfile_returns_true_for_regular_file(self): metadata = obnamlib.Metadata(st_mode=stat.S_IFREG) self.assert_(metadata.isfile()) def test_isfile_returns_false_when_st_mode_is_not_set(self): metadata = obnamlib.Metadata() self.assertFalse(metadata.isfile()) def test_has_no_md5_by_default(self): metadata = obnamlib.Metadata() self.assertEqual(metadata.md5, None) def test_sets_md5(self): metadata = obnamlib.Metadata(md5='checksum') self.assertEqual(metadata.md5, 'checksum') def test_is_equal_to_itself(self): metadata = obnamlib.Metadata(st_mode=stat.S_IFREG) self.assertEqual(metadata, metadata) def test_less_than_works(self): m1 = obnamlib.Metadata(st_size=1) m2 = obnamlib.Metadata(st_size=2) self.assert_(m1 < m2) def test_greater_than_works(self): m1 = obnamlib.Metadata(st_size=1) m2 = obnamlib.Metadata(st_size=2) self.assert_(m2 > m1) class ReadMetadataTests(unittest.TestCase): def setUp(self): self.fakefs = FakeFS() def test_returns_stat_fields_correctly(self): metadata = obnamlib.read_metadata(self.fakefs, 'foo', getpwuid=self.fakefs.getpwuid, getgrgid=self.fakefs.getgrgid) fields = ['st_atime_sec', 'st_atime_nsec', 'st_blocks', 'st_dev', 'st_gid', 'st_ino', 'st_mode', 'st_mtime_sec', 'st_mtime_nsec', 'st_nlink', 'st_size', 'st_uid', 'groupname', 'username'] for field in fields: self.assertEqual(getattr(metadata, field), getattr(self.fakefs, field), field) def test_returns_symlink_fields_correctly(self): self.fakefs.st_mode |= stat.S_IFLNK metadata = obnamlib.read_metadata(self.fakefs, 'foo', getpwuid=self.fakefs.getpwuid, getgrgid=self.fakefs.getgrgid) fields = ['st_mode', 'target'] for field in fields: self.assertEqual(getattr(metadata, field), getattr(self.fakefs, field), field) def test_reads_username_as_None_if_lookup_fails(self): metadata = obnamlib.read_metadata(self.fakefs, 'foo', getpwuid=self.fakefs.fail_getpwuid, getgrgid=self.fakefs.fail_getgrgid) self.assertEqual(metadata.username, None) class SetMetadataTests(unittest.TestCase): def setUp(self): self.metadata = obnamlib.Metadata() self.metadata.st_atime_sec = 12765 self.metadata.st_atime_nsec = 0 self.metadata.st_mode = 42 | stat.S_IFREG self.metadata.st_mtime_sec = 10**9 self.metadata.st_mtime_nsec = 0 # make sure the uid/gid magic numbers aren't the current users self.metadata.st_uid = os.getuid() + 1234 self.metadata.st_gid = 5678 while self.metadata.st_gid in os.getgroups(): self.metadata.st_gid += 1 fd, self.filename = tempfile.mkstemp() os.close(fd) # On some systems (e.g. FreeBSD) /tmp is apparently setgid and # default gid of files is therefore not the user's gid. os.chown(self.filename, os.getuid(), os.getgid()) self.fs = obnamlib.LocalFS('/') self.fs.connect() self.uid_set = None self.gid_set = None self.fs.lchown = self.fake_lchown obnamlib.set_metadata(self.fs, self.filename, self.metadata) self.st = os.stat(self.filename) def tearDown(self): self.fs.close() os.remove(self.filename) def fake_lchown(self, filename, uid, gid): # lchown(2) If the owner or group is specified as -1, # then that ID is not changed. # fake_lchown assumes it is running as root and it succeeds if uid != -1: self.uid_set = uid if gid != -1: self.gid_set = gid def oserror_lchown(self, filename, uid, gid): self.oserror_lchown_called = True raise OSError("fake permission denied") def test_sets_atime(self): self.assertEqual(self.st.st_atime, self.metadata.st_atime_sec) def test_sets_mode(self): self.assertEqual(self.st.st_mode, self.metadata.st_mode) def test_sets_mtime(self): self.assertEqual(self.st.st_mtime, self.metadata.st_mtime_sec) def test_does_not_set_uid_when_not_running_as_root(self): self.assertEqual(self.uid_set, None) def test_sets_uid_when_running_as_root(self): obnamlib.set_metadata(self.fs, self.filename, self.metadata, getuid=lambda: 0) self.assertEqual(self.uid_set, self.metadata.st_uid) def test_sets_gid_when_running_as_root(self): obnamlib.set_metadata(self.fs, self.filename, self.metadata, getuid=lambda: 0) self.assertEqual(self.gid_set, self.metadata.st_gid) def test_catches_lchown_throw(self): self.fs.lchown = self.oserror_lchown obnamlib.set_metadata(self.fs, self.filename, self.metadata) self.assert_(self.oserror_lchown_called) self.fs.lchown = self.fake_lchown def test_sets_gid_when_not_running_as_root(self): # Root can set it to any group, non-root can only set it to a group # they are part of. Set it with the real lchown and check the file. # Generally only meaningful if the user is in multiple groups. self.fs.lchown = os.lchown for gid in os.getgroups(): self.metadata.st_gid = gid obnamlib.set_metadata(self.fs, self.filename, self.metadata) self.st = os.stat(self.filename) self.assertEqual(self.st.st_gid, gid) self.fs.lchown = self.fake_lchown def test_sets_symlink_target(self): self.fs.remove(self.filename) self.metadata.st_mode = 0777 | stat.S_IFLNK self.metadata.target = 'target' obnamlib.set_metadata(self.fs, self.filename, self.metadata) self.assertEqual(self.fs.readlink(self.filename), 'target') def test_sets_symlink_mtime_perms(self): self.fs.remove(self.filename) self.metadata.st_mode = 0777 | stat.S_IFLNK self.metadata.target = 'target' obnamlib.set_metadata(self.fs, self.filename, self.metadata) st = os.lstat(self.filename) self.assertEqual(st.st_mode, self.metadata.st_mode) self.assertEqual(st.st_mtime, self.metadata.st_mtime_sec) def test_sets_setuid_and_setgid_when_told_to(self): self.metadata.st_mode = 0777 | stat.S_ISUID | stat.S_ISGID obnamlib.set_metadata(self.fs, self.filename, self.metadata, always_set_id_bits=True) st = os.stat(self.filename) self.assertEqual(st.st_mode & stat.S_ISUID, stat.S_ISUID) self.assertEqual(st.st_mode & stat.S_ISGID, stat.S_ISGID)