1# Copyright (c) 2009, Tomohiro Kusumi
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#
7# 1. Redistributions of source code must retain the above copyright notice, this
8#    list of conditions and the following disclaimer.
9# 2. Redistributions in binary form must reproduce the above copyright notice,
10#    this list of conditions and the following disclaimer in the documentation
11#    and/or other materials provided with the distribution.
12#
13# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
24import os
25import shutil
26
27from . import filebytes
28from . import fileobj
29from . import kernel
30from . import log
31from . import rrmap
32from . import util
33
34class Fileobj (rrmap.Fileobj):
35    _insert  = True
36    _replace = True
37    _delete  = True
38    _enabled = kernel.has_mremap()
39    _partial = False
40
41    def __init__(self, f, offset=0, length=0):
42        self.__dead = False
43        self.__sync = 0
44        self.__anon = None
45        super(Fileobj, self).__init__(f, offset, length)
46
47    def ctr(self):
48        if self.is_mappable():
49            super(Fileobj, self).ctr()
50        else:
51            self.__init_anon()
52            f = self.__get_backing_path()
53            self.init_mapping(f)
54            self.update_fstat(f)
55
56    def dtr(self):
57        if not self.map:
58            return
59        t = self.get_fstat()
60        self.restore_rollback_log(self)
61        if not self.__anon:
62            self.flush()
63            shcopy = False
64        else:
65            shcopy = self.__flush_anon()
66        self.cleanup_mapping()
67
68        if not self.__anon:
69            self.update_mtime(self.get_path(), t)
70        if self.__dead and not self.is_dirty():
71            f = self.__get_backing_path()
72            kernel.truncate(f)
73            self.update_mtime(f, t)
74        if self.__anon and shcopy:
75            self.__copy_anon()
76        self.__anon = None
77
78    def mmap(self, fileno):
79        return kernel.mmap_full(fileno)
80
81    def __init_anon(self):
82        assert not self.__anon
83        self.__anon = util.open_temp_file()
84        self.__anon.write(filebytes.ZERO)
85        self.__anon.seek(0)
86        self.__die()
87        log.debug("Create backing file {0} for {1}".format(
88            self.__get_backing_path(), self.get_path()))
89
90    def __copy_anon(self):
91        kernel.fsync(self.__anon)
92        src = self.__get_backing_path()
93        dst = self.get_path()
94        if src == dst:
95            return -1
96        if not os.path.isfile(src):
97            return -1
98        if kernel.read_reg_size(dst) > 0:
99            return -1
100        shutil.copy2(src, dst)
101
102    def __flush_anon(self):
103        self.sync()
104        self.clear_dirty()
105        assert self.__sync >= 1
106        return self.__sync >= 2 # need copying
107
108    def __get_backing_path(self):
109        if self.__anon:
110            return self.__anon.name
111        else:
112            return self.get_path()
113
114    def get_size(self):
115        if not self.__dead:
116            return super(Fileobj, self).get_size()
117        else:
118            return 0
119
120    def sync(self):
121        self.map.flush()
122        self.update_fstat(self.__get_backing_path())
123        self.__sync += 1
124
125    def utime(self):
126        kernel.touch(self.__get_backing_path())
127        self.update_fstat(self.__get_backing_path())
128        self.__sync += 1
129
130    def __die(self, is_dying=True):
131        if not self.__dead and is_dying:
132            kernel.touch(self.__get_backing_path())
133        self.__dead = is_dying
134
135    def insert(self, x, l, rec=True):
136        size = self.get_size()
137        n = len(l)
138        xx = x + n
139
140        if rec:
141            buf = l[:]
142            def ufn(ref):
143                ref.delete(x, n, False)
144                return x
145            def rfn(ref):
146                ref.insert(x, buf, False)
147                return x
148            self.add_undo(ufn, rfn)
149
150        self.map.resize(size + n)
151        self.map.move(xx, x, size - x)
152        self.map[x:xx] = filebytes.input_to_bytes(l)
153        self.set_dirty()
154        self.__die(False)
155
156    def replace(self, x, l, rec=True):
157        if self.is_empty():
158            self.insert(x, l, rec)
159            return
160        size = self.get_size()
161        n = len(l)
162        xx = x + n
163        resized = False
164        if x + n > size:
165            self.map.resize(x + n)
166            resized = True
167
168        if rec:
169            ubuf = filebytes.ords(self.read(x, n))
170            rbuf = l[:]
171            if not resized:
172                def ufn1(ref):
173                    ref.replace(x, ubuf, False)
174                    return x
175                def rfn1(ref):
176                    ref.replace(x, rbuf, False)
177                    return x
178                self.add_undo(ufn1, rfn1)
179            else:
180                def ufn2(ref):
181                    ref.map.resize(size) # shrink
182                    ref.replace(x, ubuf[:size - x], False)
183                    return x
184                def rfn2(ref):
185                    ref.map.resize(x + n) # expand
186                    ref.replace(x, rbuf, False)
187                    return x
188                self.add_undo(ufn2, rfn2)
189
190        self.map[x:xx] = filebytes.input_to_bytes(l)
191        self.set_dirty()
192
193    def delete(self, x, n, rec=True):
194        if self.is_empty():
195            raise fileobj.Error("Empty buffer")
196        size = self.get_size()
197        xx = x + n
198
199        if rec:
200            buf = filebytes.ords(self.read(x, n))
201            def ufn(ref):
202                ref.insert(x, buf, False)
203                return x
204            def rfn(ref):
205                ref.delete(x, n, False)
206                return x
207            self.add_undo(ufn, rfn)
208
209        self.map.move(x, xx, size - xx)
210        if size > n:
211            self.map.resize(size - n)
212        else:
213            self.__die()
214        self.set_dirty()
215