1/*
2 This file is part of Konsole, an X terminal.
3 Copyright (C) 2000 by Stephan Kulow <coolo@kde.org>
4
5 Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 02110-1301 USA.
21
22*/
23
24#include <QtDebug>
25
26// Own
27#include "BlockArray.h"
28
29// System
30#include <sys/mman.h>
31#include <sys/param.h>
32#include <unistd.h>
33#include <stdio.h>
34
35
36using namespace Konsole;
37
38static int blocksize = 0;
39
40BlockArray::BlockArray()
41 : size(0),
42 current(size_t(-1)),
43 index(size_t(-1)),
44 lastmap(0),
45 lastmap_index(size_t(-1)),
46 lastblock(0), ion(-1),
47 length(0)
48{
49 // lastmap_index = index = current = size_t(-1);
50 if (blocksize == 0) {
51 blocksize = ((sizeof(Block) / getpagesize()) + 1) * getpagesize();
52 }
53
54}
55
56BlockArray::~BlockArray()
57{
58 setHistorySize(0);
59 Q_ASSERT(!lastblock);
60}
61
62size_t BlockArray::append(Block * block)
63{
64 if (!size) {
65 return size_t(-1);
66 }
67
68 ++current;
69 if (current >= size) {
70 current = 0;
71 }
72
73 int rc;
74 rc = lseek(ion, current * blocksize, SEEK_SET);
75 if (rc < 0) {
76 perror("HistoryBuffer::add.seek");
77 setHistorySize(0);
78 return size_t(-1);
79 }
80 rc = write(ion, block, blocksize);
81 if (rc < 0) {
82 perror("HistoryBuffer::add.write");
83 setHistorySize(0);
84 return size_t(-1);
85 }
86
87 length++;
88 if (length > size) {
89 length = size;
90 }
91
92 ++index;
93
94 delete block;
95 return current;
96}
97
98size_t BlockArray::newBlock()
99{
100 if (!size) {
101 return size_t(-1);
102 }
103 append(lastblock);
104
105 lastblock = new Block();
106 return index + 1;
107}
108
109Block * BlockArray::lastBlock() const
110{
111 return lastblock;
112}
113
114bool BlockArray::has(size_t i) const
115{
116 if (i == index + 1) {
117 return true;
118 }
119
120 if (i > index) {
121 return false;
122 }
123 if (index - i >= length) {
124 return false;
125 }
126 return true;
127}
128
129const Block * BlockArray::at(size_t i)
130{
131 if (i == index + 1) {
132 return lastblock;
133 }
134
135 if (i == lastmap_index) {
136 return lastmap;
137 }
138
139 if (i > index) {
140 qDebug() << "BlockArray::at() i > index\n";
141 return 0;
142 }
143
144// if (index - i >= length) {
145// kDebug(1211) << "BlockArray::at() index - i >= length\n";
146// return 0;
147// }
148
149 size_t j = i; // (current - (index - i) + (index/size+1)*size) % size ;
150
151 Q_ASSERT(j < size);
152 unmap();
153
154 Block * block = (Block *)mmap(0, blocksize, PROT_READ, MAP_PRIVATE, ion, j * blocksize);
155
156 if (block == (Block *)-1) {
157 perror("mmap");
158 return 0;
159 }
160
161 lastmap = block;
162 lastmap_index = i;
163
164 return block;
165}
166
167void BlockArray::unmap()
168{
169 if (lastmap) {
170 int res = munmap((char *)lastmap, blocksize);
171 if (res < 0) {
172 perror("munmap");
173 }
174 }
175 lastmap = 0;
176 lastmap_index = size_t(-1);
177}
178
179bool BlockArray::setSize(size_t newsize)
180{
181 return setHistorySize(newsize * 1024 / blocksize);
182}
183
184bool BlockArray::setHistorySize(size_t newsize)
185{
186// kDebug(1211) << "setHistorySize " << size << " " << newsize;
187
188 if (size == newsize) {
189 return false;
190 }
191
192 unmap();
193
194 if (!newsize) {
195 delete lastblock;
196 lastblock = 0;
197 if (ion >= 0) {
198 close(ion);
199 }
200 ion = -1;
201 current = size_t(-1);
202 return true;
203 }
204
205 if (!size) {
206 FILE * tmp = tmpfile();
207 if (!tmp) {
208 perror("konsole: cannot open temp file.\n");
209 } else {
210 ion = dup(fileno(tmp));
211 if (ion<0) {
212 perror("konsole: cannot dup temp file.\n");
213 fclose(tmp);
214 }
215 }
216 if (ion < 0) {
217 return false;
218 }
219
220 Q_ASSERT(!lastblock);
221
222 lastblock = new Block();
223 size = newsize;
224 return false;
225 }
226
227 if (newsize > size) {
228 increaseBuffer();
229 size = newsize;
230 return false;
231 } else {
232 decreaseBuffer(newsize);
233 ftruncate(ion, length*blocksize);
234 size = newsize;
235
236 return true;
237 }
238}
239
240void moveBlock(FILE * fion, int cursor, int newpos, char * buffer2)
241{
242 int res = fseek(fion, cursor * blocksize, SEEK_SET);
243 if (res) {
244 perror("fseek");
245 }
246 res = fread(buffer2, blocksize, 1, fion);
247 if (res != 1) {
248 perror("fread");
249 }
250
251 res = fseek(fion, newpos * blocksize, SEEK_SET);
252 if (res) {
253 perror("fseek");
254 }
255 res = fwrite(buffer2, blocksize, 1, fion);
256 if (res != 1) {
257 perror("fwrite");
258 }
259 // printf("moving block %d to %d\n", cursor, newpos);
260}
261
262void BlockArray::decreaseBuffer(size_t newsize)
263{
264 if (index < newsize) { // still fits in whole
265 return;
266 }
267
268 int offset = (current - (newsize - 1) + size) % size;
269
270 if (!offset) {
271 return;
272 }
273
274 // The Block constructor could do somthing in future...
275 char * buffer1 = new char[blocksize];
276
277 FILE * fion = fdopen(dup(ion), "w+b");
278 if (!fion) {
279 delete [] buffer1;
280 perror("fdopen/dup");
281 return;
282 }
283
284 int firstblock;
285 if (current <= newsize) {
286 firstblock = current + 1;
287 } else {
288 firstblock = 0;
289 }
290
291 size_t oldpos;
292 for (size_t i = 0, cursor=firstblock; i < newsize; i++) {
293 oldpos = (size + cursor + offset) % size;
294 moveBlock(fion, oldpos, cursor, buffer1);
295 if (oldpos < newsize) {
296 cursor = oldpos;
297 } else {
298 cursor++;
299 }
300 }
301
302 current = newsize - 1;
303 length = newsize;
304
305 delete [] buffer1;
306
307 fclose(fion);
308
309}
310
311void BlockArray::increaseBuffer()
312{
313 if (index < size) { // not even wrapped once
314 return;
315 }
316
317 int offset = (current + size + 1) % size;
318 if (!offset) { // no moving needed
319 return;
320 }
321
322 // The Block constructor could do somthing in future...
323 char * buffer1 = new char[blocksize];
324 char * buffer2 = new char[blocksize];
325
326 int runs = 1;
327 int bpr = size; // blocks per run
328
329 if (size % offset == 0) {
330 bpr = size / offset;
331 runs = offset;
332 }
333
334 FILE * fion = fdopen(dup(ion), "w+b");
335 if (!fion) {
336 perror("fdopen/dup");
337 delete [] buffer1;
338 delete [] buffer2;
339 return;
340 }
341
342 int res;
343 for (int i = 0; i < runs; i++) {
344 // free one block in chain
345 int firstblock = (offset + i) % size;
346 res = fseek(fion, firstblock * blocksize, SEEK_SET);
347 if (res) {
348 perror("fseek");
349 }
350 res = fread(buffer1, blocksize, 1, fion);
351 if (res != 1) {
352 perror("fread");
353 }
354 int newpos = 0;
355 for (int j = 1, cursor=firstblock; j < bpr; j++) {
356 cursor = (cursor + offset) % size;
357 newpos = (cursor - offset + size) % size;
358 moveBlock(fion, cursor, newpos, buffer2);
359 }
360 res = fseek(fion, i * blocksize, SEEK_SET);
361 if (res) {
362 perror("fseek");
363 }
364 res = fwrite(buffer1, blocksize, 1, fion);
365 if (res != 1) {
366 perror("fwrite");
367 }
368 }
369 current = size - 1;
370 length = size;
371
372 delete [] buffer1;
373 delete [] buffer2;
374
375 fclose(fion);
376
377}
378
379