]> jfr.im git - irc/rizon/znc.git/blame - FileUtils.cpp
Write forceserver and webircpassword to conf
[irc/rizon/znc.git] / FileUtils.cpp
CommitLineData
a09a7e79 1/*
b9b0fd4c 2 * Copyright (C) 2004-2011 See the AUTHORS file for details.
a09a7e79 3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 2 as published
6 * by the Free Software Foundation.
7 */
6dcacaa7 8
b58cb3c8 9#include "FileUtils.h"
1761fe71 10#include "ExecSock.h"
73d8456d 11#include "Utils.h"
1761fe71
US
12#include "ZNCDebug.h"
13#include <errno.h>
14#include <fcntl.h>
15#include <pwd.h>
e72c4456 16#include <sys/stat.h>
17#include <sys/types.h>
d456374e 18#include <sys/wait.h>
ecf431f2 19
accb2e46 20#ifndef HAVE_LSTAT
21# define lstat(a, b) stat(a, b)
22#endif
23
e6ede6de 24#ifndef O_BINARY
25# define O_BINARY 0
26#endif
27
f9ffe6f4
US
28CString CFile::m_sHomePath;
29
ecf431f2 30CFile::CFile() {
a4ca68f6 31 m_iFD = -1;
70358cab 32 ResetError();
ecf431f2 33}
b58cb3c8 34
beb5b49b 35CFile::CFile(const CString& sLongName) {
a4ca68f6 36 m_iFD = -1;
37
70358cab 38 ResetError();
9db44ab1 39 SetFileName(sLongName);
40}
41
42CFile::~CFile() {
70b60aa4 43 Close();
9db44ab1 44}
45
46void CFile::SetFileName(const CString& sLongName) {
cb2e50a5 47 if (sLongName.Left(2) == "~/") {
f9ffe6f4 48 m_sLongName = CFile::GetHomePath() + sLongName.substr(1);
cb2e50a5
US
49 } else
50 m_sLongName = sLongName;
b58cb3c8 51
52 m_sShortName = sLongName;
653fe468 53 m_sShortName.TrimRight("/");
b58cb3c8 54
beb5b49b 55 CString::size_type uPos = m_sShortName.rfind('/');
56 if (uPos != CString::npos) {
b58cb3c8 57 m_sShortName = m_sShortName.substr(uPos +1);
58 }
59}
60
f7825e00 61bool CFile::IsDir(const CString& sLongName, bool bUseLstat) {
ada9b8cf 62 if (sLongName.Equals("/"))
63 return CFile::FType(sLongName, FT_DIRECTORY, bUseLstat);
64
f7825e00 65 // Some OS don't like trailing slashes for directories
9a8fbdb6 66 return CFile::FType(sLongName.TrimRight_n("/"),
f7825e00 67 FT_DIRECTORY, bUseLstat);
68}
69
beb5b49b 70bool CFile::IsReg(const CString& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_REGULAR, bUseLstat); }
beb5b49b 71bool CFile::IsChr(const CString& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_CHARACTER, bUseLstat); }
72bool CFile::IsBlk(const CString& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_BLOCK, bUseLstat); }
73bool CFile::IsFifo(const CString& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_FIFO, bUseLstat); }
74bool CFile::IsLnk(const CString& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_LINK, bUseLstat); }
75bool CFile::IsSock(const CString& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_SOCK, bUseLstat); }
b58cb3c8 76
81eef389 77bool CFile::IsReg(bool bUseLstat) const { return CFile::IsReg(m_sLongName, bUseLstat); }
78bool CFile::IsDir(bool bUseLstat) const { return CFile::IsDir(m_sLongName, bUseLstat); }
79bool CFile::IsChr(bool bUseLstat) const { return CFile::IsChr(m_sLongName, bUseLstat); }
80bool CFile::IsBlk(bool bUseLstat) const { return CFile::IsBlk(m_sLongName, bUseLstat); }
81bool CFile::IsFifo(bool bUseLstat) const { return CFile::IsFifo(m_sLongName, bUseLstat); }
82bool CFile::IsLnk(bool bUseLstat) const { return CFile::IsLnk(m_sLongName, bUseLstat); }
83bool CFile::IsSock(bool bUseLstat) const { return CFile::IsSock(m_sLongName, bUseLstat); }
b58cb3c8 84
b58cb3c8 85// for gettin file types, using fstat instead
7999cacf 86bool CFile::FType(const CString& sFileName, EFileTypes eType, bool bUseLstat) {
b58cb3c8 87 struct stat st;
88
89 if (!bUseLstat) {
90 if (stat(sFileName.c_str(), &st) != 0) {
91 return false;
92 }
93 } else {
94 if (lstat(sFileName.c_str(), &st) != 0) {
95 return false;
96 }
97 }
98
99 switch (eType) {
100 case FT_REGULAR:
101 return S_ISREG(st.st_mode);
102 case FT_DIRECTORY:
103 return S_ISDIR(st.st_mode);
104 case FT_CHARACTER:
105 return S_ISCHR(st.st_mode);
106 case FT_BLOCK:
107 return S_ISBLK(st.st_mode);
108 case FT_FIFO:
109 return S_ISFIFO(st.st_mode);
110 case FT_LINK:
111 return S_ISLNK(st.st_mode);
112 case FT_SOCK:
113 return S_ISSOCK(st.st_mode);
114 default:
860ccb7d 115 break;
b58cb3c8 116 }
117 return false;
118}
119
120//
121// Functions to retrieve file information
122//
123bool CFile::Exists() const { return CFile::Exists(m_sLongName); }
fa285b7c 124off_t CFile::GetSize() const { return CFile::GetSize(m_sLongName); }
5eed1f43 125time_t CFile::GetATime() const { return CFile::GetATime(m_sLongName); }
126time_t CFile::GetMTime() const { return CFile::GetMTime(m_sLongName); }
127time_t CFile::GetCTime() const { return CFile::GetCTime(m_sLongName); }
fa285b7c 128uid_t CFile::GetUID() const { return CFile::GetUID(m_sLongName); }
129gid_t CFile::GetGID() const { return CFile::GetGID(m_sLongName); }
beb5b49b 130bool CFile::Exists(const CString& sFile) {
b58cb3c8 131 struct stat st;
132 return (stat(sFile.c_str(), &st) == 0);
133}
134
fa285b7c 135off_t CFile::GetSize(const CString& sFile) {
b58cb3c8 136 struct stat st;
c64d7bc1 137 if (stat(sFile.c_str(), &st) != 0) {
b58cb3c8 138 return 0;
139 }
140
141 return (S_ISREG(st.st_mode)) ? st.st_size : 0;
142}
143
5eed1f43 144time_t CFile::GetATime(const CString& sFile) {
b58cb3c8 145 struct stat st;
146 return (stat(sFile.c_str(), &st) != 0) ? 0 : st.st_atime;
147}
148
5eed1f43 149time_t CFile::GetMTime(const CString& sFile) {
b58cb3c8 150 struct stat st;
151 return (stat(sFile.c_str(), &st) != 0) ? 0 : st.st_mtime;
152}
153
5eed1f43 154time_t CFile::GetCTime(const CString& sFile) {
b58cb3c8 155 struct stat st;
156 return (stat(sFile.c_str(), &st) != 0) ? 0 : st.st_ctime;
157}
158
fa285b7c 159uid_t CFile::GetUID(const CString& sFile) {
b58cb3c8 160 struct stat st;
161 return (stat(sFile.c_str(), &st) != 0) ? -1 : (int) st.st_uid;
162}
163
fa285b7c 164gid_t CFile::GetGID(const CString& sFile) {
b58cb3c8 165 struct stat st;
166 return (stat(sFile.c_str(), &st) != 0) ? -1 : (int) st.st_gid;
167}
beb5b49b 168int CFile::GetInfo(const CString& sFile, struct stat& st) {
b58cb3c8 169 return stat(sFile.c_str(), &st);
170}
171
172//
173// Functions to manipulate the file on the filesystem
174//
70358cab
US
175bool CFile::Delete() {
176 if (CFile::Delete(m_sLongName))
177 return true;
178 m_bHadError = true;
179 return false;
180}
181
fa285b7c 182bool CFile::Move(const CString& sNewFileName, bool bOverwrite) {
70358cab
US
183 if (CFile::Move(m_sLongName, sNewFileName, bOverwrite))
184 return true;
185 m_bHadError = true;
186 return false;
b58cb3c8 187}
188
fa285b7c 189bool CFile::Copy(const CString& sNewFileName, bool bOverwrite) {
70358cab
US
190 if (CFile::Copy(m_sLongName, sNewFileName, bOverwrite))
191 return true;
192 m_bHadError = true;
193 return false;
6be9f989 194}
195
beb5b49b 196bool CFile::Delete(const CString& sFileName) {
b58cb3c8 197 return (unlink(sFileName.c_str()) == 0) ? true : false;
198}
199
beb5b49b 200bool CFile::Move(const CString& sOldFileName, const CString& sNewFileName, bool bOverwrite) {
c64d7bc1 201 if ((!bOverwrite) && (CFile::Exists(sNewFileName))) {
8f1027d6 202 errno = EEXIST;
b58cb3c8 203 return false;
204 }
205
20f3ad92 206 return (rename(sOldFileName.c_str(), sNewFileName.c_str()) == 0);
b58cb3c8 207}
208
6be9f989 209bool CFile::Copy(const CString& sOldFileName, const CString& sNewFileName, bool bOverwrite) {
c64d7bc1 210 if ((!bOverwrite) && (CFile::Exists(sNewFileName))) {
8f1027d6 211 errno = EEXIST;
6be9f989 212 return false;
213 }
214
215 CFile OldFile(sOldFileName);
216 CFile NewFile(sNewFileName);
217
6345ce12 218 if (!OldFile.Open()) {
6be9f989 219 return false;
220 }
221
222 if (!NewFile.Open(O_WRONLY | O_CREAT | O_TRUNC)) {
223 return false;
224 }
225
226 char szBuf[8192];
e6007747 227 int len = 0;
6be9f989 228
6b4b5438 229 while ((len = OldFile.Read(szBuf, 8192))) {
e6007747 230 if (len < 0) {
235b10c2 231 DEBUG("CFile::Copy() failed: " << strerror(errno));
e6007747 232 OldFile.Close();
233
234 // That file is only a partial copy, get rid of it
235 NewFile.Close();
236 NewFile.Delete();
237
238 return false;
239 }
6be9f989 240 NewFile.Write(szBuf, len);
241 }
242
243 OldFile.Close();
244 NewFile.Close();
245
35d3ea42
AS
246 struct stat st;
247 GetInfo(sOldFileName, st);
248 Chmod(sNewFileName, st.st_mode);
249
6be9f989 250 return true;
251}
252
b58cb3c8 253bool CFile::Chmod(mode_t mode) {
69279c51 254 if (m_iFD == -1) {
8f1027d6 255 errno = EBADF;
69279c51 256 return false;
257 }
70358cab
US
258 if (fchmod(m_iFD, mode) != 0) {
259 m_bHadError = true;
260 return false;
261 }
262 return true;
b58cb3c8 263}
264
beb5b49b 265bool CFile::Chmod(const CString& sFile, mode_t mode) {
b58cb3c8 266 return (chmod(sFile.c_str(), mode) == 0);
267}
268
4e767b3e 269bool CFile::Seek(off_t uPos) {
8f1027d6
US
270 /* This sets errno in case m_iFD == -1 */
271 errno = EBADF;
272
4e767b3e 273 if (m_iFD != -1 && lseek(m_iFD, uPos, SEEK_SET) == uPos) {
ecf431f2 274 ClearBuffer();
275 return true;
276 }
70358cab
US
277 m_bHadError = true;
278
ecf431f2 279 return false;
280}
281
0a622749 282bool CFile::Truncate() {
8f1027d6
US
283 /* This sets errno in case m_iFD == -1 */
284 errno = EBADF;
285
0a622749 286 if (m_iFD != -1 && ftruncate(m_iFD, 0) == 0) {
287 ClearBuffer();
288 return true;
289 }
290
70358cab
US
291 m_bHadError = true;
292
0a622749 293 return false;
294}
295
f618ce2a 296bool CFile::Sync() {
8f1027d6
US
297 /* This sets errno in case m_iFD == -1 */
298 errno = EBADF;
299
70358cab
US
300 if (m_iFD != -1 && fsync(m_iFD) == 0)
301 return true;
302 m_bHadError = true;
303 return false;
f618ce2a 304}
305
ecf431f2 306bool CFile::Open(const CString& sFileName, int iFlags, mode_t iMode) {
307 SetFileName(sFileName);
308 return Open(iFlags, iMode);
b58cb3c8 309}
310
311bool CFile::Open(int iFlags, mode_t iMode) {
312 if (m_iFD != -1) {
8f1027d6 313 errno = EEXIST;
70358cab 314 m_bHadError = true;
b58cb3c8 315 return false;
316 }
317
36505a8d 318 // We never want to get a controlling TTY through this -> O_NOCTTY
e6ede6de 319 iMode |= O_NOCTTY;
320
321 // Some weird OS from MS needs O_BINARY or else it generates fake EOFs
322 // when reading ^Z from a file.
323 iMode |= O_BINARY;
324
325 m_iFD = open(m_sLongName.c_str(), iFlags, iMode);
70358cab
US
326 if (m_iFD < 0) {
327 m_bHadError = true;
0a622749 328 return false;
70358cab 329 }
0a622749 330
73d8456d 331 /* Make sure this FD isn't given to childs */
332 SetFdCloseOnExec(m_iFD);
333
0a622749 334 return true;
b58cb3c8 335}
336
337int CFile::Read(char *pszBuffer, int iBytes) {
338 if (m_iFD == -1) {
8f1027d6 339 errno = EBADF;
b58cb3c8 340 return -1;
341 }
342
70358cab
US
343 int res = read(m_iFD, pszBuffer, iBytes);
344 if (res != iBytes)
345 m_bHadError = true;
346 return res;
b58cb3c8 347}
348
c2b8b7c6 349bool CFile::ReadLine(CString& sData, const CString & sDelimiter) {
751f267f 350 char buff[4096];
4f6d72fe 351 int iBytes;
ecf431f2 352
b58cb3c8 353 if (m_iFD == -1) {
8f1027d6 354 errno = EBADF;
b58cb3c8 355 return false;
356 }
357
4f6d72fe 358 do {
c2b8b7c6 359 CString::size_type iFind = m_sBuffer.find(sDelimiter);
beb5b49b 360 if (iFind != CString::npos) {
751f267f 361 // We found a line, return it
90859a3f 362 sData = m_sBuffer.substr(0, iFind + sDelimiter.length());
363 m_sBuffer.erase(0, iFind + sDelimiter.length());
751f267f 364 return true;
b58cb3c8 365 }
366
4f6d72fe 367 iBytes = read(m_iFD, buff, sizeof(buff));
368
369 if (iBytes > 0) {
370 m_sBuffer.append(buff, iBytes);
b58cb3c8 371 }
4f6d72fe 372 } while (iBytes > 0);
b58cb3c8 373
751f267f 374 // We are at the end of the file or an error happened
b58cb3c8 375
90859a3f 376 if (!m_sBuffer.empty()) {
751f267f 377 // ..but there is still some partial line in the buffer
c2b8b7c6 378 sData = m_sBuffer;
379 m_sBuffer.clear();
e522c7f2 380 return true;
c2b8b7c6 381 }
382
751f267f 383 // Nothing left for reading :(
384 return false;
b58cb3c8 385}
386
b7f38c4d 387bool CFile::ReadFile(CString& sData, size_t iMaxSize) {
388 char buff[4096];
389 size_t iBytesRead = 0;
390
391 sData.clear();
392
393 while (iBytesRead < iMaxSize) {
394 int iBytes = Read(buff, sizeof(buff));
395
396 if (iBytes < 0)
397 // Error
398 return false;
399
400 if (iBytes == 0)
401 // EOF
402 return true;
403
404 sData.append(buff, iBytes);
b17bfe79 405 iBytesRead += iBytes;
b7f38c4d 406 }
407
408 // Buffer limit reached
409 return false;
410}
411
b58cb3c8 412int CFile::Write(const char *pszBuffer, u_int iBytes) {
413 if (m_iFD == -1) {
8f1027d6 414 errno = EBADF;
b58cb3c8 415 return -1;
416 }
417
70358cab
US
418 u_int res = write(m_iFD, pszBuffer, iBytes);
419 if (res != iBytes)
420 m_bHadError = true;
421 return res;
b58cb3c8 422}
423
beb5b49b 424int CFile::Write(const CString & sData) {
b58cb3c8 425 return Write(sData.data(), sData.size());
426}
a4ca68f6 427void CFile::Close() {
70b60aa4 428 if (m_iFD >= 0) {
22b219db 429 if (close(m_iFD) < 0) {
70358cab 430 m_bHadError = true;
22b219db 431 DEBUG("CFile::Close(): close() failed with ["
432 << strerror(errno) << "]");
433 }
a4ca68f6 434 }
70b60aa4 435 m_iFD = -1;
436 ClearBuffer();
a4ca68f6 437}
ecf431f2 438void CFile::ClearBuffer() { m_sBuffer.clear(); }
b58cb3c8 439
12734782 440bool CFile::TryExLock(const CString& sLockFile, int iFlags) {
441 Open(sLockFile, iFlags);
442 return TryExLock();
443}
444
445bool CFile::TryExLock() {
33ce80f4 446 return Lock(F_WRLCK, false);
447}
448
449bool CFile::ExLock() {
450 return Lock(F_WRLCK, true);
12734782 451}
452
453bool CFile::UnLock() {
33ce80f4 454 return Lock(F_UNLCK, true);
12734782 455}
456
33ce80f4 457bool CFile::Lock(int iType, bool bBlocking) {
458 struct flock fl;
459
12734782 460 if (m_iFD == -1) {
461 return false;
462 }
463
33ce80f4 464 fl.l_type = iType;
465 fl.l_whence = SEEK_SET;
466 fl.l_start = 0;
467 fl.l_len = 0;
d735e9d8 468 return (fcntl(m_iFD, (bBlocking ? F_SETLKW : F_SETLK), &fl) != -1);
12734782 469}
470
ecf431f2 471bool CFile::IsOpen() const { return (m_iFD != -1); }
beb5b49b 472CString CFile::GetLongName() const { return m_sLongName; }
473CString CFile::GetShortName() const { return m_sShortName; }
ecf431f2 474CString CFile::GetDir() const {
475 CString sDir(m_sLongName);
476
477 while (!sDir.empty() && sDir.Right(1) != "/" && sDir.Right(1) != "\\") {
478 sDir.RightChomp();
479 }
480
481 return sDir;
482}
483
f9ffe6f4
US
484void CFile::InitHomePath(const CString& sFallback) {
485 const char *home = getenv("HOME");
486
487 m_sHomePath.clear();
488 if (home) {
489 m_sHomePath = home;
490 }
491
492 if (m_sHomePath.empty()) {
493 const struct passwd* pUserInfo = getpwuid(getuid());
494
495 if (pUserInfo) {
496 m_sHomePath = pUserInfo->pw_dir;
497 }
498 }
499
500 if (m_sHomePath.empty()) {
501 m_sHomePath = sFallback;
502 }
503}
504
00613bc9 505CString CDir::ChangeDir(const CString& sPath, const CString& sAdd, const CString& sHome) {
506 CString sHomeDir(sHome);
507
508 if (sHomeDir.empty()) {
f9ffe6f4 509 sHomeDir = CFile::GetHomePath();
00613bc9 510 }
511
01bc68b1 512 if (sAdd == "~") {
513 return sHomeDir;
514 }
515
00613bc9 516 CString sAddDir(sAdd);
01bc68b1 517
518 if (sAddDir.Left(2) == "~/") {
519 sAddDir.LeftChomp();
520 sAddDir = sHomeDir + sAddDir;
521 }
522
523 CString sRet = ((sAddDir.size()) && (sAddDir[0] == '/')) ? "" : sPath;
524 sAddDir += "/";
525 CString sCurDir;
526
527 if (sRet.Right(1) == "/") {
528 sRet.RightChomp();
529 }
530
531 for (unsigned int a = 0; a < sAddDir.size(); a++) {
532 switch (sAddDir[a]) {
533 case '/':
534 if (sCurDir == "..") {
535 sRet = sRet.substr(0, sRet.rfind('/'));
536 } else if ((sCurDir != "") && (sCurDir != ".")) {
537 sRet += "/" + sCurDir;
538 }
539
540 sCurDir = "";
541 break;
542 default:
543 sCurDir += sAddDir[a];
544 break;
545 }
546 }
547
548 return (sRet.empty()) ? "/" : sRet;
549}
550
c7583c49 551CString CDir::CheckPathPrefix(const CString& sPath, const CString& sAdd, const CString& sHomeDir) {
552 CString sPrefix = sPath.Replace_n("//", "/").TrimRight_n("/") + "/";
553 CString sAbsolutePath = ChangeDir(sPrefix, sAdd, sHomeDir);
554
555 if (sAbsolutePath.Left(sPrefix.length()) != sPrefix)
556 return "";
557 return sAbsolutePath;
558}
559
b16e3ebe 560bool CDir::MakeDir(const CString& sPath, mode_t iMode) {
561 CString sDir;
562 VCString dirs;
563 VCString::iterator it;
01bc68b1 564
b16e3ebe 565 // Just in case someone tries this...
8f1027d6
US
566 if (sPath.empty()) {
567 errno = ENOENT;
b16e3ebe 568 return false;
8f1027d6 569 }
01bc68b1 570
b16e3ebe 571 // If this is an absolute path, we need to handle this now!
572 if (sPath.Left(1) == "/")
573 sDir = "/";
01bc68b1 574
b16e3ebe 575 // For every single subpath, do...
576 sPath.Split("/", dirs, false);
1d88f564 577 for (it = dirs.begin(); it != dirs.end(); ++it) {
b16e3ebe 578 // Add this to the path we already created
579 sDir += *it;
01bc68b1 580
b16e3ebe 581 int i = mkdir(sDir.c_str(), iMode);
01bc68b1 582
b16e3ebe 583 if (i != 0) {
584 // All errors except EEXIST are fatal
585 if (errno != EEXIST)
586 return false;
01bc68b1 587
b16e3ebe 588 // If it's EEXIST we have to make sure it's a dir
589 if (!CFile::IsDir(sDir))
590 return false;
01bc68b1 591 }
592
b16e3ebe 593 sDir += "/";
01bc68b1 594 }
595
b16e3ebe 596 // All went well
597 return true;
01bc68b1 598}
599
d456374e 600int CExecSock::popen2(int & iReadFD, int & iWriteFD, const CString & sCommand) {
601 int rpipes[2] = { -1, -1 };
602 int wpipes[2] = { -1, -1 };
603 iReadFD = -1;
604 iWriteFD = -1;
605
b490b120 606 if (pipe(rpipes) < 0)
607 return -1;
608
609 if (pipe(wpipes) < 0) {
610 close(rpipes[0]);
611 close(rpipes[1]);
612 return -1;
613 }
f74ab87e 614
d456374e 615 int iPid = fork();
616
617 if (iPid == -1) {
b490b120 618 close(rpipes[0]);
619 close(rpipes[1]);
620 close(wpipes[0]);
621 close(wpipes[1]);
d456374e 622 return -1;
623 }
624
625 if (iPid == 0) {
626 close(wpipes[1]);
627 close(rpipes[0]);
628 dup2(wpipes[0], 0);
629 dup2(rpipes[1], 1);
630 dup2(rpipes[1], 2);
631 close(wpipes[0]);
632 close(rpipes[1]);
b490b120 633 const char * pArgv[] =
775df4f8 634 {
635 "sh",
636 "-c",
b490b120 637 sCommand.c_str(),
775df4f8 638 NULL
639 };
b490b120 640 execvp("sh", (char * const *) pArgv);
422ab328 641 // if execvp returns, there was an error
642 perror("execvp");
643 exit(1);
d456374e 644 }
645
646 close(wpipes[0]);
647 close(rpipes[1]);
648
649 iWriteFD = wpipes[1];
650 iReadFD = rpipes[0];
651
652 return iPid;
653}
654
655void CExecSock::close2(int iPid, int iReadFD, int iWriteFD) {
b0a1714b 656 close(iReadFD);
657 close(iWriteFD);
0ad7756e 658 u_int iNow = time(NULL);
659 while (waitpid(iPid, NULL, WNOHANG) == 0) {
660 if ((time(NULL) - iNow) > 5)
661 break; // giveup
662 usleep(100);
663 }
d456374e 664 return;
665}