1/****************************************************************************
2**
3** Copyright (C) 2007 Christian Ehrlicher <ch.ehrlicher@gmx.de>.
4** All rights reserved.
5**
6** This file is part of the KDE installer for windows
7**
8** This library is free software; you can redistribute it and/or
9** modify it under the terms of the GNU Library General Public
10** License as published by the Free Software Foundation; either
11** version 2 of the License, or (at your option) any later version.
12**
13** This library is distributed in the hope that it will be useful,
14** but WITHOUT ANY WARRANTY; without even the implied warranty of
15** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16** Library General Public License for more details.
17**
18** You should have received a copy of the GNU Library General Public License
19** along with this library; see the file COPYING.LIB. If not, write to
20** the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21** Boston, MA 02110-1301, USA.
22**
23** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
24** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
25**
26****************************************************************************/
27
28#include <QBuffer>
29#include <QFile>
30#include <QIODevice>
31#include <QFileInfo>
32#include <QDateTime>
33#include <QTextCodec>
34
35#include <time.h>
36
37#include "tarfilter.h"
38
39#ifndef Q_OS_WIN
40 #include <pwd.h>
41 #include <grp.h>
42 #include <unistd.h>
43#endif
44
45// TODO: proper cleanup states on error
46// add full support than the default one used by tar
47// add support for special devices & links
48
49//
50// TarFilter::Private
51//
52class TarFilter::Private
53{
54 public:
55 Private(QIODevice *dev, QTextCodec *c)
56 : device(dev),
57 codec(c ? c : QTextCodec::codecForLocale()),
58 iBufSize(1024*1024),
59 nextChunkSize(-1)
60 {}
61
62 struct tar_posix_header
63 {
64 char name[100];
65 char mode[8];
66 char uid[8];
67 char gid[8];
68 char size[12];
69 char mtime[12];
70 char chksum[8];
71 char typeflag;
72 char linkname[100];
73 char magic[6];
74 char version[2];
75 char uname[32];
76 char gname[32];
77 char devmajor[8];
78 char devminor[8];
79 char prefix[155];
80 char unused[12]; // to align to 512bytes
81 };
82 bool fi2tph(const FileInformations &infos); // converts FileInformations to tar_posix_header and adds them to QIODevice
83 bool addHeader(const FileInformations &infos); // adds a complete header for FileInformations
84
85 bool tph2fi(FileInformations &infos); // reads a tar_posix_header and convert it to FileInformations
86 bool getHeader(FileInformations &infos); // reads a header and converts them to FileInformations
87 bool checkDevice(QIODevice *in, QIODevice *out);
88 bool copyData(QIODevice *in, QIODevice *out, qint64 iSize);
89
90 QIODevice *device;
91 QTextCodec *codec;
92 QString lastError;
93 unsigned int iBufSize;
94 qint64 nextChunkSize;
95};
96
97bool TarFilter::Private::fi2tph(const FileInformations &infos)
98{
99 QByteArray fn = codec->fromUnicode(infos.fileName);
100#ifdef Q_OS_WIN
101 if(fn.size() > 3 && fn[1] == ':' && (fn[2] == '\\' || fn[2] == '/'))
102 fn = fn.mid(3);
103#endif
104 if(fn.startsWith('/'))
105 fn = fn.mid(1);
106
107 long unsigned int fileSize = infos.fileType == directory ? 0 : infos.fileSize;
108 long unsigned int mtime = infos.mtime != -1 ? infos.mtime : time(NULL);
109 tar_posix_header hdr;
110 memset(&hdr, 0, sizeof(hdr));
111 QByteArray fn_part1 = fn.left(sizeof(hdr.name));
112 memcpy(hdr.name, fn_part1.constData(), fn_part1.size());
113 sprintf(hdr.mode, "%07x", 0x0fff & infos.mode);
114 sprintf(hdr.uid, "%07o", infos.uid);
115 sprintf(hdr.gid, "%07o", infos.gid);
116 sprintf(hdr.size, "%011lo", fileSize);
117 sprintf(hdr.mtime, "%011lo", mtime);
118 memset(hdr.chksum, ' ', sizeof(hdr.chksum)); // 8 * ' '
119 hdr.typeflag = infos.fileType;
120 //memset(hdr.linkname, 0, sizeof(hdr.linkname));
121 memcpy(hdr.magic, "ustar ", sizeof(hdr.magic) + sizeof(hdr.version));
122#ifdef Q_OS_WIN
123 //memset(hdr.uname, 0, sizeof(hdr.uname));
124 //memset(hdr.gname, 0, sizeof(hdr.gname));
125#else
126 qstrncpy(hdr.uname, codec->fromUnicode(infos.uname).constData(), sizeof(hdr.uname));
127 qstrncpy(hdr.gname, codec->fromUnicode(infos.gname).constData(), sizeof(hdr.gname));
128#endif
129 //memset(hdr.devmajor, 0, sizeof(hdr.devmajor));
130 //memset(hdr.devminor, 0, sizeof(hdr.devminor));
131 //memset(hdr.prefix, 0, sizeof(hdr.prefix));
132
133 int checksum = 0;
134 const unsigned char *ptr = (const unsigned char*)(&hdr);
135 for (unsigned i = 0; i < sizeof(hdr); i++)
136 checksum += ptr[i];
137 sprintf(hdr.chksum, "%06o", checksum);
138
139 if(!device->write((const char*)&hdr, sizeof(hdr))) {
140 lastError = QLatin1String("Error writing to QIODevice");
141 return false;
142 }
143 return true;
144}
145
146bool TarFilter::Private::addHeader(const FileInformations &infos)
147{
148 // TODO: do some sanity checks (e.g. for filesize)
149
150 QByteArray fn = codec->fromUnicode(infos.fileName);
151#ifdef Q_OS_WIN
152 if(fn.size() > 3 && fn[1] == ':' && (fn[2] == '\\' || fn[2] == '/'))
153 fn = fn.mid(3);
154#endif
155 if(fn.startsWith('/'))
156 fn = fn.mid(1);
157 if(fn.size() > 512) {
158 lastError = QLatin1String("Can't handle filenames with more then 512 bytes");
159 return false;
160 }
161 if(fn.size() > 100) {
162 FileInformations longlink;
163 longlink.fileName = QLatin1String("././@LongLink");
164 longlink.fileType = gnu_longname;
165 longlink.fileSize = strlen(fn.constData()) + 1; // including '\0' ?
166 longlink.mtime = 0;
167 longlink.uid = 0;
168 longlink.gid = 0;
169 longlink.mode = 0;
170#ifndef Q_OS_WIN
171 struct passwd *p = getpwuid(longlink.uid);
172 if(p)
173 longlink.uname = codec->toUnicode(p->pw_name);
174 struct group *g = getgrgid(longlink.gid);
175 if(g)
176 longlink.gname = codec->toUnicode(g->gr_name);
177#endif
178 fi2tph(longlink);
179 QByteArray ba(512, '\0');
180 qstrncpy(ba.data(), fn.constData(), ba.size());
181 if(!device->write(ba.constData(), ba.size())) {
182 lastError = QLatin1String("Error writing to QIODevice");
183 return false;
184 }
185 }
186 if(!fi2tph(infos))
187 return false;
188
189 return true;
190}
191
192bool TarFilter::Private::tph2fi(FileInformations &infos)
193{
194 Private::tar_posix_header hdr;
195 do {
196 int iBytesRead = device->read((char*)&hdr, sizeof(hdr));
197 if(iBytesRead != sizeof(hdr)) {
198 if(iBytesRead == 0 && device->atEnd()) {
199 lastError = QString();
200 return false;
201 }
202 lastError = QString(QLatin1String("Error reading from QIODevice! needed: %1, got: %2")).arg(sizeof(hdr)).arg(iBytesRead);
203 return false;
204 }
205 } while ( memcmp(hdr.magic, "\0\0\0\0\0\0", sizeof(hdr.magic)) == 0 );
206 if(memcmp(hdr.magic, "ustar ", sizeof(hdr.magic))) {
207 lastError = QLatin1String("The archive is not supported");
208 return false;
209 }
210 int checksum = QByteArray(hdr.chksum, sizeof(hdr.chksum)).toUInt(NULL, 8);
211 memset(hdr.chksum, ' ', sizeof(hdr.chksum));
212 int realChecksum = 0;
213 const unsigned char *ptr = (const unsigned char*)(&hdr);
214 for (unsigned i = 0; i < sizeof(hdr); i++)
215 realChecksum += ptr[i];
216 if(checksum != realChecksum) {
217 lastError = QString(QLatin1String("Checksum does not match! needed: %1, got: %2")).arg(checksum).arg(realChecksum);
218 return false;
219 }
220
221 QByteArray ba(hdr.name, sizeof(hdr.name));
222 ba.resize(qstrlen(ba.constData()));
223 infos.fileName = codec->toUnicode(ba);
224 infos.fileSize = QByteArray(hdr.size, sizeof(hdr.size)).toUInt(NULL, 8);
225 infos.mode = static_cast<QFile::Permissions>(QByteArray(hdr.mode, sizeof(hdr.mode)).toUInt(NULL, 16));
226 infos.uid = QByteArray(hdr.uid, sizeof(hdr.uid)).toUInt(NULL, 8);
227 infos.gid = QByteArray(hdr.gid, sizeof(hdr.gid)).toUInt(NULL, 8);
228 infos.mtime = QByteArray(hdr.mtime, sizeof(hdr.mtime)).toUInt(NULL, 8);
229 infos.fileType = (FileType)hdr.typeflag;
230 infos.uname = codec->toUnicode(hdr.uname, sizeof(hdr.uname));
231 infos.gname = codec->toUnicode(hdr.gname, sizeof(hdr.gname));
232
233 return true;
234}
235
236bool TarFilter::Private::getHeader(FileInformations &infos)
237{
238 if(!tph2fi(infos))
239 return false;
240 switch(infos.fileType) {
241 case regular:
242 case regular2:
243 case directory:
244 case gnu_longname:
245 break;
246 default:
247 lastError = QString(QLatin1String("Unsupported fileType %1")).arg(infos.fileType);
248 return false;
249 }
250 if(infos.fileType == gnu_longname) {
251 QByteArray fn = device->read(512);
252 if(fn.size() != 512) {
253 lastError = QLatin1String("Unexpected end of inputstream");
254 return false;
255 }
256 if(!tph2fi(infos))
257 return false;
258 int len = qstrlen(fn);
259 infos.fileName = codec->toUnicode(fn.constData(), len);
260 }
261
262 return true;
263}
264
265bool TarFilter::Private::checkDevice(QIODevice *in, QIODevice *out)
266{
267 if(!in) {
268 lastError = QLatin1String("No QIODevice for reading available");
269 return false;
270 }
271 if((in->openMode() & QIODevice::ReadOnly) == 0) {
272 lastError = QLatin1String("QIODevice not opened for reading");
273 return false;
274 }
275
276 if(!out) {
277 lastError = QLatin1String("No QIODevice for writing available");
278 return false;
279 }
280 if((out->openMode() & QIODevice::WriteOnly) == 0) {
281 lastError = QLatin1String("QIODevice not opened for writing");
282 return false;
283 }
284 return true;
285}
286
287bool TarFilter::Private::copyData(QIODevice *in, QIODevice *out, qint64 iSize)
288{
289 if(iSize <= 0)
290 return true;
291
292 int iBytesToRead = 1;
293 while(iSize > 0) {
294 iBytesToRead = iSize > iBufSize ? iBufSize : iSize;
295
296 const QByteArray ba = in->read(iBytesToRead);
297 if(ba.size() == 0) {
298 lastError = QString(QLatin1String("Could not read %1 bytes from QIODevice")).arg(iBytesToRead);
299 return false;
300 }
301 if(out->write(ba) != ba.size()) {
302 lastError = QString(QLatin1String("Could not write %1 bytes to QIODevice")).arg(ba.size());
303 return false;
304 }
305 iSize -= ba.size();
306 }
307 return true;
308}
309
310//
311// TarFilter
312//
313TarFilter::TarFilter(QIODevice *dev, QTextCodec *codec)
314 : d(new Private(dev, codec))
315{}
316
317TarFilter::~TarFilter()
318{
319 delete d;
320}
321
322void TarFilter::setLocaleCodec(QTextCodec *codec)
323{
324 d->codec = codec ? codec : QTextCodec::codecForLocale();
325}
326
327void TarFilter::setBufferSize(unsigned int size)
328{
329 d->iBufSize = size > 1024 ? size : 1024;
330}
331
332bool TarFilter::error() const
333{
334 return !d->lastError.isEmpty();
335}
336
337QString TarFilter::lastError() const
338{
339 return d->lastError;
340}
341
342bool TarFilter::addFile(const QString &filename, const QString &filenameInArchive)
343{
344 QFileInfo fi(filename);
345 if(!fi.exists()) {
346 d->lastError = QString(QLatin1String("File %1 does not exist")).arg(filename);
347 return false;
348 }
349 if(!(fi.isDir() || fi.isFile())) {
350 d->lastError = QString(QLatin1String("%1 isn't a directory or a regular file")).arg(filename);
351 return false;
352 }
353
354 FileInformations fInfos;
355 fInfos.fileName = (filenameInArchive.isEmpty() ? filename : filenameInArchive);
356 fInfos.fileSize = fi.isDir() ? 0 : fi.size();
357 fInfos.mode = fi.permissions() & 0x0fff;
358#ifdef Q_OS_WIN
359 fInfos.uid = 1000;
360 fInfos.gid = 100;
361#else
362 fInfos.uid = fi.ownerId();
363 fInfos.gid = fi.groupId();
364 fInfos.uname = fi.owner();
365 fInfos.gname = fi.group();
366#endif
367 fInfos.mtime = fi.lastModified().toTime_t();
368 fInfos.fileType = fi.isDir() ? directory : regular;
369
370 if(fi.isDir()) {
371 QBuffer buf;
372 buf.open(QIODevice::ReadOnly);
373 return addData(fInfos, &buf);
374 }
375 QFile f(filename);
376 if(!f.open(QIODevice::ReadOnly)) {
377 d->lastError = QString(QLatin1String("Error opening %1 for reading")).arg(filename);
378 return false;
379 }
380 return addData(fInfos, &f);
381}
382
383bool TarFilter::addData(const QString &filename, const QByteArray &data, bool bIsDir)
384{
385 FileInformations fi;
386 fi.fileName = filename;
387 fi.fileSize = bIsDir ? 0 : data.size();
388 fi.mode = static_cast<QFile::Permissions>(bIsDir ? 0x0755 : 0x0644);
389 fi.fileType = bIsDir ? directory : regular;
390#ifndef Q_OS_WIN
391 fi.uid = getuid();
392 fi.gid = getgid();
393 struct passwd *p = getpwuid(fi.uid);
394 if(p)
395 fi.uname = d->codec->toUnicode(p->pw_name);
396 struct group *g = getgrgid(fi.gid);
397 if(g)
398 fi.gname = d->codec->toUnicode(g->gr_name);
399#endif
400
401 QBuffer buf(const_cast<QByteArray*>(&data));
402 if(!buf.open(QIODevice::ReadOnly)) {
403 d->lastError = QLatin1String("Internal Error - can't open QBuffer for reading!");
404 return false;
405 }
406 return addData(fi, &buf);
407}
408
409bool TarFilter::addData(const FileInformations &fi, QIODevice *in)
410{
411 if(!d->checkDevice(in, d->device))
412 return false;
413
414 if(!d->addHeader(fi))
415 return false;
416
417 if(fi.fileSize) {
418 if(!d->copyData(in, d->device, fi.fileSize))
419 return false;
420
421 int align = fi.fileSize % 512;
422 if(align) {
423 align = 512 - align;
424 QByteArray ba2(align, '\0');
425 d->device->write(ba2.constData(), align);
426 }
427 }
428 return true;
429}
430
431bool TarFilter::writeEOS()
432{
433 const int size = sizeof(TarFilter::Private::tar_posix_header);
434 QByteArray ba(size, '\0');
435 return(d->device->write(ba) == size);
436}
437
438bool TarFilter::getData(FileInformations &infos)
439{
440 d->nextChunkSize = -1;
441 if(!d->getHeader(infos))
442 return false;
443 d->nextChunkSize = infos.fileSize;
444 return true;
445}
446
447bool TarFilter::getData(QIODevice *out)
448{
449 if(!d->checkDevice(d->device, out))
450 return false;
451
452 if(d->nextChunkSize < 0) {
453 d->lastError = QLatin1String("No informations about next chunk size. Did you call getData(FileInformations &infos) before?");
454 return false;
455 }
456
457 if(!d->copyData(d->device, out, d->nextChunkSize))
458 return false;
459 int align = d->nextChunkSize % 512;
460 if(align)
461 d->device->read(512 - align);
462
463 d->nextChunkSize = -1;
464 return true;
465}
466
467bool TarFilter::getData(FileInformations &fi, QByteArray &data)
468{
469 if(!getData(fi))
470 return false;
471
472 QBuffer buf(&data);
473 buf.open(QIODevice::WriteOnly);
474 return getData(&buf);
475}
476
477bool TarFilter::getData(FileInformations &fi, QIODevice *out)
478{
479 if(!getData(fi))
480 return false;
481
482 return getData(out);
483}
484