1 /* This file is part of pr-downloader (GPL v2 or later), see the LICENSE file */
2 
3 #include "File.h"
4 #include "FileSystem.h"
5 #include "Logger.h"
6 #include "IHash.h"
7 
8 #include <stdio.h>
9 #include <unistd.h>
10 #include <errno.h>
11 #include <string.h>
12 #include <sys/stat.h>
13 #include <stdlib.h>
14 #include <sys/time.h>
15 
16 #ifdef WIN32
17 #include <windows.h>
18 #endif
19 
CFile()20 CFile::CFile():
21 	handle(NULL),
22 	piecesize(-1),
23 	size(-1),
24 	curpos(0),
25 	isnewfile(true),
26 	timestamp(0)
27 {
28 }
29 
~CFile()30 CFile::~CFile()
31 {
32 	//TODO: write buffered data
33 	Close();
34 }
Close()35 void CFile::Close()
36 {
37 	if (handle!=0) {
38 		fclose(handle);
39 		if (IsNewFile()) {
40 			unlink(filename.c_str()); //delete possible existing destination file
41 			rename(tmpfile.c_str(), filename.c_str());
42 			isnewfile = false;
43 		}
44 		handle=0;
45 	}
46 }
47 
Open(const std::string & filename,long size,int piecesize)48 bool CFile::Open(const std::string& filename, long size, int piecesize)
49 {
50 	LOG_DEBUG("%s %d %d", filename.c_str(), size, piecesize);
51 	this->filename=filename;
52 	this->size=size;
53 	fileSystem->createSubdirs(filename);
54 	SetPieceSize(piecesize);
55 //	fileSystem->createSubdirs(filename);
56 	if (handle!=NULL) {
57 		LOG_ERROR("file opened before old was closed");
58 		return false;
59 	}
60 	struct stat sb;
61 	int res=stat(filename.c_str(), &sb);
62 	timestamp = 0;
63 	isnewfile=res!=0;
64 	if (isnewfile) { //if file is new, create it, if not, open the existing one without truncating it
65 		tmpfile = filename + ".tmp";
66 		handle=fopen(tmpfile.c_str(), "wb+");
67 	} else {
68 		handle=fopen(filename.c_str(), "rb+");
69 		timestamp = sb.st_mtime;
70 	}
71 	if (handle==0) {
72 		LOG_ERROR("open(%s): %s",filename.c_str(), strerror(errno));
73 		return false;
74 	}
75 
76 	if((!isnewfile) && (size>0) && (size!=sb.st_size)) { //truncate file if real-size != excepted file size
77 		int ret=ftruncate(fileno(handle), size);
78 		if (ret!=0) {
79 			LOG_ERROR("ftruncate failed");
80 		}
81 		LOG_ERROR("File already exists but file-size missmatched");
82 	} else if (size<=0) {
83 		//TODO: allocate disk space
84 	}
85 	LOG_INFO("opened %s", filename.c_str());
86 	return true;
87 }
88 
Hash(IHash & hash,int piece)89 bool CFile::Hash(IHash& hash, int piece)
90 {
91 //	LOG("Hash() piece %d", piece);
92 	char buf[IO_BUF_SIZE];
93 	SetPos(0, piece);
94 	hash.Init();
95 //	LOG("piece %d left: %d",piece,  GetPieceSize(piece));
96 	int read=0;
97 	long unsigned left=GetPieceSize(piece); //total bytes to hash
98 	if (left==0) {
99 		LOG_ERROR("tried to hash empty piece %d", piece);
100 		return false;
101 	}
102 
103 	while(left>0) {
104 		int toread=std::min(left, (long unsigned)sizeof(buf));
105 		read=Read(buf, toread, piece);
106 		if(read<=0) {
107 			LOG_ERROR("EOF or read error on piece %d, left: %d toread: %d size: %d, GetPiecePos %d GetPieceSize(): %d read: %d", piece, left, toread, GetPieceSize(piece), GetPiecePos(piece), GetPieceSize(piece), read);
108 			LOG_ERROR("curpos: %d", curpos);
109 			return false;
110 		}
111 		hash.Update(buf, toread);
112 		left=left-toread;
113 	}
114 	hash.Final();
115 	SetPos(0, piece);
116 //	LOG("CFile::Hash(): %s piece:%d", hash.toString().c_str(), piece);
117 	return true;
118 }
119 
Read(char * buf,int bufsize,int piece)120 int CFile::Read(char *buf, int bufsize, int piece)
121 {
122 	SetPos(GetPiecePos(piece), piece);
123 //	LOG("Read(%d) bufsize: %d GetPiecePos(): %d GetPieceSize() %d",piece, bufsize, GetPiecePos(piece), GetPieceSize(piece));
124 	clearerr(handle);
125 	int items=fread(buf, bufsize, 1, handle);
126 	if (items<=0) {
127 		if(ferror(handle)) {
128 			LOG_ERROR("read error %s bufsize: %d curpos: %d GetPieceSize: %d", strerror(errno), bufsize, curpos, GetPieceSize());
129 			SetPos(0, piece);
130 			return -1;
131 		}
132 		if(feof(handle)) {
133 			LOG_ERROR("EOF while Read: '%s' items: %d!", strerror(errno), items);
134 			LOG_ERROR("read error %s bufsize: %d curpos: %d GetPieceSize: %d", strerror(errno), bufsize, curpos, GetPieceSize());
135 			return -1;
136 		}
137 	}
138 	SetPos(GetPiecePos(piece)+bufsize, piece); //inc pos
139 	return bufsize;
140 }
141 
SetPos(long pos,int piece)142 void CFile::SetPos(long pos, int piece)
143 {
144 //	LOG("SetPos() pos %d piece%d", pos, piece);
145 	if (piece>=0) {
146 		//LOG_DEBUG("pos: %d piecesize: %d", pos, piecesize);
147 		assert(pieces[piece].pos<=size+pos);
148 		//assert(pos<=piecesize); This is no longer needed since we download multiple pieces together
149 		pieces[piece].pos =pos;
150 	} else {
151 		assert(size<=0 || pos<=size);
152 		curpos = pos;
153 	}
154 	Seek(pos, piece);
155 }
156 
Write(const char * buf,int bufsize,int piece)157 int CFile::Write(const char*buf, int bufsize, int piece)
158 {
159 	SetPos(GetPiecePos(piece), piece);
160 	clearerr(handle);
161 //	LOG("Write() bufsize %d piece %d handle %d", bufsize, piece, fileno(handle));
162 	int res=fwrite(buf, bufsize, 1, handle);
163 	if (res!=1)
164 		LOG_ERROR("write error %d", res);
165 //	LOG("wrote bufsize %d", bufsize);
166 	if(ferror(handle)!=0) {
167 		LOG_ERROR("Error in write(): %s", strerror(errno));
168 		abort();
169 	}
170 	if(feof(handle)) {
171 		LOG_ERROR("EOF in write(): %s", strerror(errno));
172 	}
173 	SetPos(GetPiecePos(piece)+bufsize, piece);
174 	/*	if ((piece>=0) && (GetPiecePos(piece)==GetPieceSize(piece))) {
175 			LOG("piece finished: %d", piece);
176 		}
177 	*/
178 	return bufsize;
179 }
180 
181 
Seek(unsigned long pos,int piece)182 int CFile::Seek(unsigned long pos, int piece)
183 {
184 	assert(piece<=(int)pieces.size());
185 	if(piece>=0) { //adjust position relative to piece pos
186 		pos=this->piecesize*piece+pos;
187 	}
188 //	LOG("Seek() pos: %d piece: %d", pos, piece);
189 	clearerr(handle);
190 	if (fseek(handle, pos, SEEK_SET)!=0) {
191 		LOG_ERROR("seek error %ld", pos);
192 	}
193 	return pos;
194 }
195 
SetPieceSize(int pieceSize)196 bool CFile::SetPieceSize(int pieceSize)
197 {
198 	assert(handle==NULL); //this function has to be called before the file is opened
199 	pieces.clear();
200 	if ((size<=0) || (pieceSize<=0)) {
201 		LOG_DEBUG("SetPieceSize(): FileSize:%ld PieceSize: %d", size, pieceSize);
202 		return false;
203 	}
204 	if (size<pieceSize) {
205 		pieceSize = size;
206 		LOG_DEBUG("SetPieceSize(): forcing lower pieceSize: %d", pieceSize);
207 	}
208 	unsigned count=this->size/pieceSize;
209 	if (count==pieces.size()) //check if size is already correct
210 		return true;
211 	pieces.clear();
212 	if(this->size%pieceSize>0)
213 		count++;
214 	if(count==0) {
215 		LOG_ERROR("SetPieceSize(): count==0");
216 		return false;
217 	}
218 	for(unsigned i=0; i<count; i++) {
219 		pieces.push_back(CFilePiece());
220 	}
221 	piecesize=pieceSize;
222 	curpos=0;
223 //	LOG("SetPieceSize piecesize: %d filesize: %ld pieces count:%d", pieceSize, this->size, (int)pieces.size());
224 	return true;
225 }
GetPiecesSize(std::vector<unsigned int> pieces) const226 int CFile::GetPiecesSize(std::vector< unsigned int > pieces) const
227 {
228   int size = 0;
229   for ( std::vector< unsigned int >::iterator it = pieces.begin(); it != pieces.end(); it++ )
230   {
231     size += GetPieceSize(*it);
232   }
233   return size;
234 
235 }
236 
GetPieceSize(int piece) const237 int CFile::GetPieceSize(int piece) const
238 {
239 	if (piece>=0) {
240 		assert(piece<=(int)pieces.size());
241 		if ((int)pieces.size()-1==piece) //last piece
242 			return size-(piecesize*((int)pieces.size()-1));
243 //		LOG("GetPieceSize piece %d, pieces.size() %d piecesize: %d size %d ", piece, pieces.size(),piecesize, size);
244 		return piecesize;
245 	}
246 	if (size<0) {
247 		return GetSizeFromHandle();
248 	}
249 	return size;
250 }
251 
GetPiecePos(int piece) const252 long CFile::GetPiecePos(int piece) const
253 {
254 	assert(piece<=(int)pieces.size());
255 	if (piece>=0)
256 		return pieces[piece].pos;
257 	return curpos;
258 }
259 
GetSizeFromHandle() const260 long CFile::GetSizeFromHandle() const
261 {
262 	if(handle==NULL) {
263 		LOG_ERROR("GetSize(): file isn't opened!");
264 		return -1;
265 	}
266 
267 	struct stat sb;
268 	if (fstat(fileno(handle), &sb)!=0) {
269 		LOG_ERROR("CFile::SetSize(): fstat failed");
270 		return -1;
271 	}
272 //	LOG("GetSizeFromHandle: %d blocks: %d", sb.st_size, sb.st_blocks);
273 	return sb.st_size;
274 }
275 
IsNewFile()276 bool CFile::IsNewFile()
277 {
278 	return isnewfile;
279 }
280 
GetTimestamp()281 long CFile::GetTimestamp()
282 {
283 	return timestamp;
284 }
285 
SetTimestamp(long timestamp)286 bool CFile::SetTimestamp(long timestamp)
287 {
288 #ifdef WIN32
289 	FILETIME ftime;
290 	HANDLE h;
291 	bool close = false;
292 	if (handle==NULL) {
293 		h = CreateFile(filename.c_str() , GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
294 		close = true;
295 	} else {
296 		h = (HANDLE)_get_osfhandle(fileno(handle));
297 	}
298 	if (h == NULL) {
299 		return false;
300 	}
301 	fileSystem->TimestampToFiletime(timestamp, ftime);
302 	bool ret = SetFileTime(h, NULL, NULL, &ftime);
303 	if (close) { //close opened file
304 		CloseHandle(h);
305 	}
306 	return ret;
307 #else
308 	struct timeval tv = {0, 0};
309 	tv.tv_sec = timestamp;
310 	if (handle==NULL) {
311 		return lutimes(filename.c_str(), &tv) == 0;
312 	} else {
313 		return futimes(fileno(handle), &tv) == 0;
314 	}
315 #endif
316 }
317