Files
znc/src/FileUtils.cpp
T
Ryan S e73060ad59 Unblock signals when spawning child processes.
The signal mask is inherited by children, so if we don't remove it
the shell module spawns processes which are accidentally resistant
to most signals.

see: man sigprocmask (znc uses pthread_sigmask, but they act the same)
2018-08-03 09:14:41 -07:00

684 lines
17 KiB
C++

/*
* Copyright (C) 2004-2018 ZNC, see the NOTICE file for details.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <znc/FileUtils.h>
#include <znc/ExecSock.h>
#include <znc/Utils.h>
#include <fcntl.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#ifndef HAVE_LSTAT
#define lstat(a, b) stat(a, b)
#endif
#ifndef O_BINARY
#define O_BINARY 0
#endif
CString CFile::m_sHomePath;
CFile::CFile() : CFile("") {}
CFile::CFile(const CString& sLongName)
: m_sBuffer(""),
m_iFD(-1),
m_bHadError(false),
m_sLongName(""),
m_sShortName("") {
SetFileName(sLongName);
}
CFile::~CFile() { Close(); }
void CFile::SetFileName(const CString& sLongName) {
if (sLongName.StartsWith("~/")) {
m_sLongName = CFile::GetHomePath() + sLongName.substr(1);
} else
m_sLongName = sLongName;
m_sShortName = sLongName;
m_sShortName.TrimRight("/");
CString::size_type uPos = m_sShortName.rfind('/');
if (uPos != CString::npos) {
m_sShortName = m_sShortName.substr(uPos + 1);
}
}
bool CFile::IsDir(const CString& sLongName, bool bUseLstat) {
if (sLongName.Equals("/"))
return CFile::FType(sLongName, FT_DIRECTORY, bUseLstat);
// Some OS don't like trailing slashes for directories
return CFile::FType(sLongName.TrimRight_n("/"), FT_DIRECTORY, bUseLstat);
}
bool CFile::IsReg(const CString& sLongName, bool bUseLstat) {
return CFile::FType(sLongName, FT_REGULAR, bUseLstat);
}
bool CFile::IsChr(const CString& sLongName, bool bUseLstat) {
return CFile::FType(sLongName, FT_CHARACTER, bUseLstat);
}
bool CFile::IsBlk(const CString& sLongName, bool bUseLstat) {
return CFile::FType(sLongName, FT_BLOCK, bUseLstat);
}
bool CFile::IsFifo(const CString& sLongName, bool bUseLstat) {
return CFile::FType(sLongName, FT_FIFO, bUseLstat);
}
bool CFile::IsLnk(const CString& sLongName, bool bUseLstat) {
return CFile::FType(sLongName, FT_LINK, bUseLstat);
}
bool CFile::IsSock(const CString& sLongName, bool bUseLstat) {
return CFile::FType(sLongName, FT_SOCK, bUseLstat);
}
bool CFile::IsReg(bool bUseLstat) const {
return CFile::IsReg(m_sLongName, bUseLstat);
}
bool CFile::IsDir(bool bUseLstat) const {
return CFile::IsDir(m_sLongName, bUseLstat);
}
bool CFile::IsChr(bool bUseLstat) const {
return CFile::IsChr(m_sLongName, bUseLstat);
}
bool CFile::IsBlk(bool bUseLstat) const {
return CFile::IsBlk(m_sLongName, bUseLstat);
}
bool CFile::IsFifo(bool bUseLstat) const {
return CFile::IsFifo(m_sLongName, bUseLstat);
}
bool CFile::IsLnk(bool bUseLstat) const {
return CFile::IsLnk(m_sLongName, bUseLstat);
}
bool CFile::IsSock(bool bUseLstat) const {
return CFile::IsSock(m_sLongName, bUseLstat);
}
// for gettin file types, using fstat instead
bool CFile::FType(const CString& sFileName, EFileTypes eType, bool bUseLstat) {
struct stat st;
if (!bUseLstat) {
if (stat(sFileName.c_str(), &st) != 0) {
return false;
}
} else {
if (lstat(sFileName.c_str(), &st) != 0) {
return false;
}
}
switch (eType) {
case FT_REGULAR:
return S_ISREG(st.st_mode);
case FT_DIRECTORY:
return S_ISDIR(st.st_mode);
case FT_CHARACTER:
return S_ISCHR(st.st_mode);
case FT_BLOCK:
return S_ISBLK(st.st_mode);
case FT_FIFO:
return S_ISFIFO(st.st_mode);
case FT_LINK:
return S_ISLNK(st.st_mode);
case FT_SOCK:
return S_ISSOCK(st.st_mode);
default:
break;
}
return false;
}
//
// Functions to retrieve file information
//
bool CFile::Exists() const { return CFile::Exists(m_sLongName); }
off_t CFile::GetSize() const { return CFile::GetSize(m_sLongName); }
time_t CFile::GetATime() const { return CFile::GetATime(m_sLongName); }
time_t CFile::GetMTime() const { return CFile::GetMTime(m_sLongName); }
time_t CFile::GetCTime() const { return CFile::GetCTime(m_sLongName); }
uid_t CFile::GetUID() const { return CFile::GetUID(m_sLongName); }
gid_t CFile::GetGID() const { return CFile::GetGID(m_sLongName); }
bool CFile::Exists(const CString& sFile) {
struct stat st;
return (stat(sFile.c_str(), &st) == 0);
}
off_t CFile::GetSize(const CString& sFile) {
struct stat st;
if (stat(sFile.c_str(), &st) != 0) {
return 0;
}
return (S_ISREG(st.st_mode)) ? st.st_size : 0;
}
time_t CFile::GetATime(const CString& sFile) {
struct stat st;
return (stat(sFile.c_str(), &st) != 0) ? 0 : st.st_atime;
}
time_t CFile::GetMTime(const CString& sFile) {
struct stat st;
return (stat(sFile.c_str(), &st) != 0) ? 0 : st.st_mtime;
}
time_t CFile::GetCTime(const CString& sFile) {
struct stat st;
return (stat(sFile.c_str(), &st) != 0) ? 0 : st.st_ctime;
}
uid_t CFile::GetUID(const CString& sFile) {
struct stat st;
return (stat(sFile.c_str(), &st) != 0) ? -1 : (int)st.st_uid;
}
gid_t CFile::GetGID(const CString& sFile) {
struct stat st;
return (stat(sFile.c_str(), &st) != 0) ? -1 : (int)st.st_gid;
}
int CFile::GetInfo(const CString& sFile, struct stat& st) {
return stat(sFile.c_str(), &st);
}
//
// Functions to manipulate the file on the filesystem
//
bool CFile::Delete() {
if (CFile::Delete(m_sLongName)) return true;
m_bHadError = true;
return false;
}
bool CFile::Move(const CString& sNewFileName, bool bOverwrite) {
if (CFile::Move(m_sLongName, sNewFileName, bOverwrite)) return true;
m_bHadError = true;
return false;
}
bool CFile::Copy(const CString& sNewFileName, bool bOverwrite) {
if (CFile::Copy(m_sLongName, sNewFileName, bOverwrite)) return true;
m_bHadError = true;
return false;
}
bool CFile::Delete(const CString& sFileName) {
return (unlink(sFileName.c_str()) == 0) ? true : false;
}
bool CFile::Move(const CString& sOldFileName, const CString& sNewFileName,
bool bOverwrite) {
if (CFile::Exists(sNewFileName)) {
if (!bOverwrite) {
errno = EEXIST;
return false;
}
#ifdef _WIN32
// rename() never overwrites files on Windows.
DWORD dFlags = MOVEFILE_WRITE_THROUGH | MOVEFILE_COPY_ALLOWED |
MOVEFILE_REPLACE_EXISTING;
return (::MoveFileExA(sOldFileName.c_str(), sNewFileName.c_str(),
dFlags) != 0);
#endif
}
return (rename(sOldFileName.c_str(), sNewFileName.c_str()) == 0);
}
bool CFile::Copy(const CString& sOldFileName, const CString& sNewFileName,
bool bOverwrite) {
if ((!bOverwrite) && (CFile::Exists(sNewFileName))) {
errno = EEXIST;
return false;
}
CFile OldFile(sOldFileName);
CFile NewFile(sNewFileName);
if (!OldFile.Open()) {
return false;
}
if (!NewFile.Open(O_WRONLY | O_CREAT | O_TRUNC)) {
return false;
}
char szBuf[8192];
ssize_t len = 0;
while ((len = OldFile.Read(szBuf, 8192))) {
if (len < 0) {
DEBUG("CFile::Copy() failed: " << strerror(errno));
OldFile.Close();
// That file is only a partial copy, get rid of it
NewFile.Close();
NewFile.Delete();
return false;
}
NewFile.Write(szBuf, len);
}
OldFile.Close();
NewFile.Close();
struct stat st;
GetInfo(sOldFileName, st);
Chmod(sNewFileName, st.st_mode);
return true;
}
bool CFile::Chmod(mode_t mode) {
if (m_iFD == -1) {
errno = EBADF;
return false;
}
if (fchmod(m_iFD, mode) != 0) {
m_bHadError = true;
return false;
}
return true;
}
bool CFile::Chmod(const CString& sFile, mode_t mode) {
return (chmod(sFile.c_str(), mode) == 0);
}
bool CFile::Seek(off_t uPos) {
/* This sets errno in case m_iFD == -1 */
errno = EBADF;
if (m_iFD != -1 && lseek(m_iFD, uPos, SEEK_SET) == uPos) {
ClearBuffer();
return true;
}
m_bHadError = true;
return false;
}
bool CFile::Truncate() {
/* This sets errno in case m_iFD == -1 */
errno = EBADF;
if (m_iFD != -1 && ftruncate(m_iFD, 0) == 0) {
ClearBuffer();
return true;
}
m_bHadError = true;
return false;
}
bool CFile::Sync() {
/* This sets errno in case m_iFD == -1 */
errno = EBADF;
if (m_iFD != -1 && fsync(m_iFD) == 0) return true;
m_bHadError = true;
return false;
}
bool CFile::Open(const CString& sFileName, int iFlags, mode_t iMode) {
SetFileName(sFileName);
return Open(iFlags, iMode);
}
bool CFile::Open(int iFlags, mode_t iMode) {
if (m_iFD != -1) {
errno = EEXIST;
m_bHadError = true;
return false;
}
// We never want to get a controlling TTY through this -> O_NOCTTY
iFlags |= O_NOCTTY;
// Some weird OS from MS needs O_BINARY or else it generates fake EOFs
// when reading ^Z from a file.
iFlags |= O_BINARY;
m_iFD = open(m_sLongName.c_str(), iFlags, iMode);
if (m_iFD < 0) {
m_bHadError = true;
return false;
}
/* Make sure this FD isn't given to childs */
SetFdCloseOnExec(m_iFD);
return true;
}
ssize_t CFile::Read(char* pszBuffer, int iBytes) {
if (m_iFD == -1) {
errno = EBADF;
return -1;
}
ssize_t res = read(m_iFD, pszBuffer, iBytes);
if (res != iBytes) m_bHadError = true;
return res;
}
bool CFile::ReadLine(CString& sData, const CString& sDelimiter) {
char buff[4096];
ssize_t iBytes;
if (m_iFD == -1) {
errno = EBADF;
return false;
}
do {
CString::size_type iFind = m_sBuffer.find(sDelimiter);
if (iFind != CString::npos) {
// We found a line, return it
sData = m_sBuffer.substr(0, iFind + sDelimiter.length());
m_sBuffer.erase(0, iFind + sDelimiter.length());
return true;
}
iBytes = read(m_iFD, buff, sizeof(buff));
if (iBytes > 0) {
m_sBuffer.append(buff, iBytes);
}
} while (iBytes > 0);
// We are at the end of the file or an error happened
if (!m_sBuffer.empty()) {
// ..but there is still some partial line in the buffer
sData = m_sBuffer;
m_sBuffer.clear();
return true;
}
// Nothing left for reading :(
return false;
}
bool CFile::ReadFile(CString& sData, size_t iMaxSize) {
char buff[4096];
size_t iBytesRead = 0;
sData.clear();
while (iBytesRead < iMaxSize) {
ssize_t iBytes = Read(buff, sizeof(buff));
if (iBytes < 0)
// Error
return false;
if (iBytes == 0)
// EOF
return true;
sData.append(buff, iBytes);
iBytesRead += iBytes;
}
// Buffer limit reached
return false;
}
ssize_t CFile::Write(const char* pszBuffer, size_t iBytes) {
if (m_iFD == -1) {
errno = EBADF;
return -1;
}
ssize_t res = write(m_iFD, pszBuffer, iBytes);
if (-1 == res) m_bHadError = true;
return res;
}
ssize_t CFile::Write(const CString& sData) {
return Write(sData.data(), sData.size());
}
void CFile::Close() {
if (m_iFD >= 0) {
if (close(m_iFD) < 0) {
m_bHadError = true;
DEBUG("CFile::Close(): close() failed with [" << strerror(errno)
<< "]");
}
}
m_iFD = -1;
ClearBuffer();
}
void CFile::ClearBuffer() { m_sBuffer.clear(); }
bool CFile::TryExLock(const CString& sLockFile, int iFlags) {
return Open(sLockFile, iFlags) && TryExLock();
}
bool CFile::TryExLock() { return Lock(F_WRLCK, false); }
bool CFile::ExLock() { return Lock(F_WRLCK, true); }
bool CFile::UnLock() { return Lock(F_UNLCK, true); }
bool CFile::Lock(short iType, bool bBlocking) {
struct flock fl;
if (m_iFD == -1) {
return false;
}
fl.l_type = iType;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
return (fcntl(m_iFD, (bBlocking ? F_SETLKW : F_SETLK), &fl) != -1);
}
bool CFile::IsOpen() const { return (m_iFD != -1); }
CString CFile::GetLongName() const { return m_sLongName; }
CString CFile::GetShortName() const { return m_sShortName; }
CString CFile::GetDir() const {
CString sDir(m_sLongName);
while (!sDir.empty() && !sDir.EndsWith("/") && !sDir.EndsWith("\\")) {
sDir.RightChomp();
}
return sDir;
}
void CFile::InitHomePath(const CString& sFallback) {
const char* home = getenv("HOME");
m_sHomePath.clear();
if (home) {
m_sHomePath = home;
}
if (m_sHomePath.empty()) {
const struct passwd* pUserInfo = getpwuid(getuid());
if (pUserInfo) {
m_sHomePath = pUserInfo->pw_dir;
}
}
if (m_sHomePath.empty()) {
m_sHomePath = sFallback;
}
}
CString CDir::ChangeDir(const CString& sPath, const CString& sAdd,
const CString& sHome) {
CString sHomeDir(sHome);
if (sHomeDir.empty()) {
sHomeDir = CFile::GetHomePath();
}
if (sAdd == "~") {
return sHomeDir;
}
CString sAddDir(sAdd);
if (sAddDir.StartsWith("~/")) {
sAddDir.LeftChomp();
sAddDir = sHomeDir + sAddDir;
}
CString sRet = ((sAddDir.size()) && (sAddDir[0] == '/')) ? "" : sPath;
sAddDir += "/";
CString sCurDir;
sRet.TrimSuffix("/");
for (unsigned int a = 0; a < sAddDir.size(); a++) {
switch (sAddDir[a]) {
case '/':
if (sCurDir == "..") {
sRet = sRet.substr(0, sRet.rfind('/'));
} else if ((sCurDir != "") && (sCurDir != ".")) {
sRet += "/" + sCurDir;
}
sCurDir = "";
break;
default:
sCurDir += sAddDir[a];
break;
}
}
return (sRet.empty()) ? "/" : sRet;
}
CString CDir::CheckPathPrefix(const CString& sPath, const CString& sAdd,
const CString& sHomeDir) {
CString sPrefix = sPath.Replace_n("//", "/").TrimRight_n("/") + "/";
CString sAbsolutePath = ChangeDir(sPrefix, sAdd, sHomeDir);
if (!sAbsolutePath.StartsWith(sPrefix)) return "";
return sAbsolutePath;
}
bool CDir::MakeDir(const CString& sPath, mode_t iMode) {
CString sDir;
VCString dirs;
VCString::iterator it;
// Just in case someone tries this...
if (sPath.empty()) {
errno = ENOENT;
return false;
}
// If this is an absolute path, we need to handle this now!
if (sPath.StartsWith("/")) sDir = "/";
// For every single subpath, do...
sPath.Split("/", dirs, false);
for (it = dirs.begin(); it != dirs.end(); ++it) {
// Add this to the path we already created
sDir += *it;
int i = mkdir(sDir.c_str(), iMode);
if (i != 0) {
// All errors except EEXIST are fatal
if (errno != EEXIST) return false;
// If it's EEXIST we have to make sure it's a dir
if (!CFile::IsDir(sDir)) return false;
}
sDir += "/";
}
// All went well
return true;
}
int CExecSock::popen2(int& iReadFD, int& iWriteFD, const CString& sCommand) {
int rpipes[2] = {-1, -1};
int wpipes[2] = {-1, -1};
iReadFD = -1;
iWriteFD = -1;
if (pipe(rpipes) < 0) return -1;
if (pipe(wpipes) < 0) {
close(rpipes[0]);
close(rpipes[1]);
return -1;
}
int iPid = fork();
if (iPid == -1) {
close(rpipes[0]);
close(rpipes[1]);
close(wpipes[0]);
close(wpipes[1]);
return -1;
}
if (iPid == 0) {
sigset_t signals;
sigemptyset(&signals);
sigprocmask(SIG_SETMASK, &signals, nullptr);
close(wpipes[1]);
close(rpipes[0]);
dup2(wpipes[0], 0);
dup2(rpipes[1], 1);
dup2(rpipes[1], 2);
close(wpipes[0]);
close(rpipes[1]);
const char* pArgv[] = {"sh", "-c", sCommand.c_str(), nullptr};
execvp("sh", (char* const*)pArgv);
// if execvp returns, there was an error
perror("execvp");
exit(1);
}
close(wpipes[0]);
close(rpipes[1]);
iWriteFD = wpipes[1];
iReadFD = rpipes[0];
return iPid;
}
void CExecSock::close2(int iPid, int iReadFD, int iWriteFD) {
close(iReadFD);
close(iWriteFD);
time_t iNow = time(nullptr);
while (waitpid(iPid, nullptr, WNOHANG) == 0) {
if ((time(nullptr) - iNow) > 5) break; // giveup
usleep(100);
}
return;
}