ef1e94eee2
On Linux, fadvise forces unwritten pages to be flushed to disk before returning. This means that under heavy disk IO, all threads could be waiting on the fadvise call. This diff uses atomic operations to ensure that only one thread can do IO at any given time. In addition, it makes any fadvise calls tail the end of the file by 1 MB. This reduces the chance that fadvise will trigger IO at all.
928 linhas
24 KiB
C++
928 linhas
24 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "util.h"
|
|
#include "hphp/util/base.h"
|
|
#include "hphp/util/lock.h"
|
|
#include "hphp/util/logger.h"
|
|
#include "hphp/util/exception.h"
|
|
#include "hphp/util/network.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <libgen.h>
|
|
#include <execinfo.h>
|
|
#include <cxxabi.h>
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static Mutex s_file_mutex;
|
|
|
|
void Util::split(char delimiter, const char *s, vector<string> &out,
|
|
bool ignoreEmpty /* = false */) {
|
|
assert(s);
|
|
|
|
const char *start = s;
|
|
const char *p = s;
|
|
for (; *p; p++) {
|
|
if (*p == delimiter) {
|
|
if (!ignoreEmpty || p > start) {
|
|
out.push_back(string(start, p - start));
|
|
}
|
|
start = p + 1;
|
|
}
|
|
}
|
|
if (!ignoreEmpty || p > start) {
|
|
out.push_back(string(start, p - start));
|
|
}
|
|
}
|
|
|
|
void Util::replaceAll(string &s, const char *from, const char *to) {
|
|
assert(from && *from);
|
|
assert(to);
|
|
|
|
string::size_type lenFrom = strlen(from);
|
|
string::size_type lenTo = strlen(to);
|
|
for (string::size_type pos = s.find(from);
|
|
pos != string::npos;
|
|
pos = s.find(from, pos + lenTo)) {
|
|
s.replace(pos, lenFrom, to);
|
|
}
|
|
}
|
|
|
|
std::string Util::toLower(const std::string &s) {
|
|
unsigned int len = s.size();
|
|
string ret;
|
|
if (len) {
|
|
ret.reserve(len);
|
|
for (unsigned int i = 0; i < len; i++) {
|
|
ret += tolower(s[i]);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::string Util::toUpper(const std::string &s) {
|
|
unsigned int len = s.size();
|
|
string ret;
|
|
ret.reserve(len);
|
|
for (unsigned int i = 0; i < len; i++) {
|
|
ret += toupper(s[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::string Util::getIdentifier(const std::string &fileName) {
|
|
string ret = "hphp_" + fileName;
|
|
replaceAll(ret, "/", "__");
|
|
replaceAll(ret, ".", "__");
|
|
replaceAll(ret, "-", "__");
|
|
return ret;
|
|
}
|
|
|
|
bool Util::mkdir(const std::string &path, int mode /* = 0777 */) {
|
|
if (path.empty()) {
|
|
return false;
|
|
}
|
|
size_t pos = path.rfind('/');
|
|
if (pos != string::npos) {
|
|
// quick test whole path exists
|
|
if (access(path.substr(0, pos).c_str(), F_OK) >= 0) {
|
|
return true;
|
|
}
|
|
for (pos = path.find('/'); pos != string::npos;
|
|
pos = path.find('/', pos + 1)) {
|
|
string subpath = path.substr(0, pos);
|
|
if (subpath.empty()) continue;
|
|
if (access(subpath.c_str(), F_OK) < 0 &&
|
|
::mkdir(subpath.c_str(), mode) < 0) {
|
|
Logger::Error("unable to mkdir %s", subpath.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool same(const char *file1, const char *file2) {
|
|
FILE *f1 = fopen(file1, "r");
|
|
if (f1 == nullptr) {
|
|
Logger::Error("unable to read %s", file1);
|
|
return false;
|
|
}
|
|
FILE *f2 = fopen(file2, "r");
|
|
if (f2 == nullptr) {
|
|
fclose(f1);
|
|
Logger::Error("unable to read %s", file2);
|
|
return false;
|
|
}
|
|
|
|
bool ret = false;
|
|
char buf1[8192];
|
|
char buf2[sizeof(buf1)];
|
|
int n1;
|
|
while ((n1 = fread(buf1, 1, sizeof(buf1), f1))) {
|
|
int toread = n1;
|
|
int pos = 0;
|
|
while (toread) {
|
|
int n2 = fread(buf2 + pos, 1, toread, f2);
|
|
if (n2 <= 0) {
|
|
goto exit_false;
|
|
}
|
|
toread -= n2;
|
|
pos += n2;
|
|
}
|
|
if (memcmp(buf1, buf2, n1) != 0) {
|
|
goto exit_false;
|
|
}
|
|
}
|
|
if (fread(buf2, 1, 1, f2) == 0) {
|
|
ret = true;
|
|
}
|
|
exit_false:
|
|
fclose(f2);
|
|
fclose(f1);
|
|
return ret;
|
|
}
|
|
|
|
void Util::syncdir(const std::string &dest_, const std::string &src_,
|
|
bool keepSrc /* = false */) {
|
|
if (src_.empty() || dest_.empty()) return;
|
|
|
|
string src = src_;
|
|
if (src[src.size() - 1] != '/') src += '/';
|
|
string dest = dest_;
|
|
if (dest[dest.size() - 1] != '/') dest += '/';
|
|
|
|
DIR *ddest = opendir(dest.c_str());
|
|
if (ddest == nullptr) {
|
|
Logger::Error("syncdir: unable to open dest %s", dest.c_str());
|
|
return;
|
|
}
|
|
|
|
DIR *dsrc = opendir(src.c_str());
|
|
if (dsrc == nullptr) {
|
|
closedir(ddest);
|
|
Logger::Error("syncdir: unable to open src %s", src.c_str());
|
|
return;
|
|
}
|
|
|
|
dirent *e;
|
|
|
|
std::set<string> todelete;
|
|
while ((e = readdir(ddest))) {
|
|
if (strcmp(e->d_name, ".") == 0 ||
|
|
strcmp(e->d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
string fsrc = src + e->d_name;
|
|
string fdest = dest + e->d_name;
|
|
|
|
// delete files/directories that are only in dest
|
|
if (access(fsrc.c_str(), F_OK) < 0) {
|
|
size_t pos = fdest.rfind('.');
|
|
if (pos != string::npos) {
|
|
string ext = fdest.substr(pos + 1);
|
|
// do not delete intermediate files
|
|
if (ext == "o" || ext == "d") {
|
|
continue;
|
|
}
|
|
}
|
|
todelete.insert(fdest);
|
|
continue;
|
|
}
|
|
|
|
// delete mismatched types so to copy over new ones
|
|
struct stat sb1, sb2;
|
|
stat(fsrc.c_str(), &sb1);
|
|
stat(fdest.c_str(), &sb2);
|
|
if ((sb1.st_mode & S_IFMT) != (sb2.st_mode & S_IFMT)) {
|
|
todelete.insert(fdest.c_str());
|
|
continue;
|
|
}
|
|
|
|
// updates
|
|
if ((sb1.st_mode & S_IFMT) == S_IFDIR) {
|
|
syncdir(fdest, fsrc);
|
|
} else if (sb1.st_size != sb2.st_size ||
|
|
!same(fsrc.c_str(), fdest.c_str())) {
|
|
todelete.insert(fdest);
|
|
}
|
|
}
|
|
|
|
// delete the ones to delete
|
|
if (!todelete.empty()) {
|
|
for (std::set<string>::const_iterator iter = todelete.begin();
|
|
iter != todelete.end(); ++iter) {
|
|
Logger::Info("sync: deleting %s", iter->c_str());
|
|
boost::filesystem::remove_all(*iter);
|
|
}
|
|
}
|
|
|
|
// insert new ones
|
|
while ((e = readdir(dsrc))) {
|
|
string fdest = dest + e->d_name;
|
|
if (access(fdest.c_str(), F_OK) < 0) {
|
|
Logger::Info("sync: updating %s", fdest.c_str());
|
|
if (keepSrc) {
|
|
ssystem((string("cp -R ") + src + e->d_name + " " + dest).c_str());
|
|
} else {
|
|
rename((src + e->d_name).c_str(), (dest + e->d_name).c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
closedir(dsrc);
|
|
closedir(ddest);
|
|
}
|
|
|
|
int Util::copy(const char *srcfile, const char *dstfile) {
|
|
int srcFd = open(srcfile, O_RDONLY);
|
|
if (srcFd == -1) return -1;
|
|
int dstFd = open(dstfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
if (dstFd == -1) return -1;
|
|
|
|
while (1) {
|
|
char buf[8192];
|
|
bool err = false;
|
|
ssize_t rbytes = read(srcFd, buf, sizeof(buf));
|
|
ssize_t wbytes;
|
|
if (rbytes == 0) break;
|
|
if (rbytes == -1) {
|
|
err = true;
|
|
Logger::Error("read failed: %s", safe_strerror(errno).c_str());
|
|
} else if ((wbytes = write(dstFd, buf, rbytes)) != rbytes) {
|
|
err = true;
|
|
Logger::Error("write failed: %zd, %s", wbytes,
|
|
safe_strerror(errno).c_str());
|
|
}
|
|
if (err) {
|
|
close(srcFd);
|
|
close(dstFd);
|
|
return -1;
|
|
}
|
|
}
|
|
close(srcFd);
|
|
close(dstFd);
|
|
return 0;
|
|
}
|
|
|
|
static int force_sync(int fd) {
|
|
#if defined(__FreeBSD__) || defined(__APPLE__)
|
|
return fsync(fd);
|
|
#else
|
|
return fdatasync(fd);
|
|
#endif
|
|
}
|
|
|
|
int Util::drop_cache(int fd, off_t len /* = 0 */) {
|
|
#if defined(__FreeBSD__) || defined(__APPLE__)
|
|
return 0;
|
|
#else
|
|
return posix_fadvise(fd, 0, len, POSIX_FADV_DONTNEED);
|
|
#endif
|
|
}
|
|
|
|
int Util::drop_cache(FILE *f, off_t len /* = 0 */) {
|
|
return drop_cache(fileno(f), len);
|
|
}
|
|
|
|
|
|
int LogFileFlusher::DropCacheChunkSize = (1 << 20);
|
|
void LogFileFlusher::recordWriteAndMaybeDropCaches(int fd, int bytes) {
|
|
int oldBytesWritten = m_bytesWritten.fetch_add(bytes);
|
|
int newBytesWritten = oldBytesWritten + bytes;
|
|
|
|
if (!(newBytesWritten > DropCacheChunkSize &&
|
|
oldBytesWritten <= DropCacheChunkSize)) {
|
|
return;
|
|
}
|
|
|
|
off_t offset = lseek(fd, 0, SEEK_CUR);
|
|
if (offset > kDropCacheTail) {
|
|
dropCache(fd, offset - kDropCacheTail);
|
|
}
|
|
|
|
m_bytesWritten = 0;
|
|
}
|
|
|
|
int Util::directCopy(const char *srcfile, const char *dstfile) {
|
|
int srcFd = open(srcfile, O_RDONLY);
|
|
if (srcFd == -1) return -1;
|
|
int dstFd = open(dstfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
if (dstFd == -1) return -1;
|
|
|
|
#if defined(__APPLE__)
|
|
fcntl(srcFd, F_NOCACHE, 1);
|
|
fcntl(dstFd, F_NOCACHE, 1);
|
|
#endif
|
|
|
|
while (1) {
|
|
char buf[1 << 20];
|
|
bool err = false;
|
|
ssize_t rbytes = read(srcFd, buf, sizeof(buf));
|
|
ssize_t wbytes;
|
|
if (rbytes == 0) break;
|
|
|
|
if (rbytes == -1) {
|
|
err = true;
|
|
Logger::Error("read failed: %s", safe_strerror(errno).c_str());
|
|
} else if (force_sync(srcFd) == -1) {
|
|
err = true;
|
|
Logger::Error("read sync failed: %s",
|
|
safe_strerror(errno).c_str());
|
|
} else if (drop_cache(srcFd) == -1) {
|
|
err = true;
|
|
Logger::Error("read cache drop failed: %s",
|
|
safe_strerror(errno).c_str());
|
|
} else if ((wbytes = write(dstFd, buf, rbytes)) != rbytes) {
|
|
err = true;
|
|
Logger::Error("write failed: %zd, %s", wbytes,
|
|
safe_strerror(errno).c_str());
|
|
} else if (force_sync(dstFd) == -1) {
|
|
err = true;
|
|
Logger::Error("write sync failed: %s",
|
|
safe_strerror(errno).c_str());
|
|
} else if (drop_cache(dstFd) == -1) {
|
|
err = true;
|
|
Logger::Error("write cache drop failed: %s",
|
|
safe_strerror(errno).c_str());
|
|
}
|
|
if (err) {
|
|
close(srcFd);
|
|
close(dstFd);
|
|
return -1;
|
|
}
|
|
}
|
|
close(srcFd);
|
|
close(dstFd);
|
|
return 0;
|
|
}
|
|
|
|
int Util::rename(const char *oldname, const char *newname) {
|
|
int ret = ::rename(oldname, newname);
|
|
if (ret == 0) return 0;
|
|
if (errno != EXDEV) return -1;
|
|
|
|
copy(oldname, newname);
|
|
unlink(oldname);
|
|
return 0;
|
|
}
|
|
|
|
int Util::directRename(const char *oldname, const char *newname) {
|
|
int ret = ::rename(oldname, newname);
|
|
if (ret == 0) return 0;
|
|
if (errno != EXDEV) return -1;
|
|
|
|
directCopy(oldname, newname);
|
|
unlink(oldname);
|
|
return 0;
|
|
}
|
|
|
|
int Util::ssystem(const char* command) {
|
|
int ret = system(command);
|
|
if (ret == -1) {
|
|
Logger::Error("system(\"%s\"): %s", command, safe_strerror(errno).c_str());
|
|
} else if (ret != 0) {
|
|
Logger::Error("command failed: \"%s\"", command);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::string Util::safe_strerror(int errnum) {
|
|
char buf[1024];
|
|
#ifdef __GLIBC__
|
|
return strerror_r(errnum, buf, sizeof(buf));
|
|
#else
|
|
strerror_r(errnum, buf, sizeof(buf));
|
|
return buf;
|
|
#endif
|
|
}
|
|
|
|
size_t Util::dirname_helper(char *path, int len) {
|
|
if (len == 0) {
|
|
/* Illegal use of this function */
|
|
return 0;
|
|
}
|
|
|
|
/* Strip trailing slashes */
|
|
register char *end = path + len - 1;
|
|
while (end >= path && *end == '/') {
|
|
end--;
|
|
}
|
|
if (end < path) {
|
|
/* The path only contained slashes */
|
|
path[0] = '/';
|
|
path[1] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
/* Strip filename */
|
|
while (end >= path && *end != '/') {
|
|
end--;
|
|
}
|
|
if (end < path) {
|
|
/* No slash found, therefore return '.' */
|
|
path[0] = '.';
|
|
path[1] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
/* Strip slashes which came before the file name */
|
|
while (end >= path && *end == '/') {
|
|
end--;
|
|
}
|
|
if (end < path) {
|
|
path[0] = '/';
|
|
path[1] = '\0';
|
|
return 1;
|
|
}
|
|
*(end+1) = '\0';
|
|
|
|
return end + 1 - path;
|
|
}
|
|
|
|
std::string Util::safe_dirname(const char *path, int len) {
|
|
char* tmp_path = (char*)malloc(len+1);
|
|
memcpy(tmp_path, path, len);
|
|
tmp_path[len] = '\0';
|
|
size_t newLen = dirname_helper(tmp_path, len);
|
|
std::string ret;
|
|
ret.assign(tmp_path, newLen);
|
|
free((void *) tmp_path);
|
|
return ret;
|
|
}
|
|
|
|
std::string Util::safe_dirname(const char *path) {
|
|
int len = strlen(path);
|
|
return safe_dirname(path, len);
|
|
}
|
|
|
|
std::string Util::safe_dirname(const std::string& path) {
|
|
return safe_dirname(path.c_str(), path.size());
|
|
}
|
|
|
|
std::string Util::relativePath(const std::string fromDir,
|
|
const std::string toFile) {
|
|
|
|
size_t maxlen = (fromDir.size() + toFile.size()) * 3;
|
|
|
|
// Sanity checks
|
|
if (fromDir[0] != '/' || toFile[0] != '/' ||
|
|
fromDir[fromDir.size() - 1] != '/') {
|
|
return "";
|
|
}
|
|
|
|
// Maybe we're lucky and this is an easy case
|
|
int from_len = fromDir.size();
|
|
if (strncmp(toFile.c_str(), fromDir.c_str(), from_len) == 0) {
|
|
return toFile.substr(from_len);
|
|
}
|
|
|
|
char* path = (char*) malloc(maxlen);
|
|
int path_len = 0;
|
|
|
|
const char* from_dir = fromDir.c_str();
|
|
const char* to_file = toFile.c_str();
|
|
|
|
const char* from_start = from_dir + 1;
|
|
const char* to_start = to_file + 1;
|
|
|
|
while (true) {
|
|
int seg_len = 0;
|
|
char cur = from_start[0];
|
|
while (cur && cur != '/') {
|
|
++seg_len;
|
|
cur = from_start[seg_len];
|
|
}
|
|
|
|
if (memcmp(from_start, to_start, seg_len + 1)) {
|
|
break;
|
|
}
|
|
from_start += seg_len + 1;
|
|
to_start += seg_len + 1;
|
|
}
|
|
|
|
// Now to build the path
|
|
char cur = *from_start;
|
|
char* path_end = path;
|
|
while (cur) {
|
|
if (cur == '/') {
|
|
strcpy(path_end, "../");
|
|
path_len += 3;
|
|
maxlen -= 3;
|
|
path_end += 3;
|
|
}
|
|
++from_start;
|
|
cur = *from_start;
|
|
}
|
|
|
|
if (from_start[-1] != '/') {
|
|
strcpy(path_end, "../");
|
|
path_len += 3;
|
|
maxlen -= 3;
|
|
path_end += 3;
|
|
}
|
|
|
|
strncpy(path_end, to_start, maxlen - 1);
|
|
|
|
string p(path);
|
|
free((void*) path);
|
|
|
|
return p;
|
|
}
|
|
|
|
std::string Util::canonicalize(const std::string &path) {
|
|
const char *r = canonicalize(path.c_str(), path.size());
|
|
string res(r);
|
|
free((void*)r);
|
|
return res;
|
|
}
|
|
|
|
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You 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.
|
|
*/
|
|
|
|
const char *Util::canonicalize(const char *addpath, size_t addlen,
|
|
bool collapse_slashes /* = true */) {
|
|
assert(strlen(addpath) == addlen);
|
|
// 4 for slashes at start, after root, and at end, plus trailing
|
|
// null
|
|
size_t maxlen = addlen + 4;
|
|
size_t pathlen = 0; // is the length of the result path
|
|
size_t seglen; // is the end of the current segment
|
|
|
|
/* Treat null as an empty path.
|
|
*/
|
|
if (!addpath)
|
|
addpath = "";
|
|
|
|
char *path = (char *)malloc(maxlen);
|
|
|
|
if (addpath[0] == '/' && collapse_slashes) {
|
|
/* Ignore the given root path, strip off leading
|
|
* '/'s to a single leading '/' from the addpath,
|
|
* and leave addpath at the first non-'/' character.
|
|
*/
|
|
while (addpath[0] == '/')
|
|
++addpath;
|
|
path[0] = '/';
|
|
pathlen = 1;
|
|
}
|
|
|
|
while (*addpath) {
|
|
/* Parse each segment, find the closing '/'
|
|
*/
|
|
const char *next = addpath;
|
|
while (*next && (*next != '/')) {
|
|
++next;
|
|
}
|
|
seglen = next - addpath;
|
|
|
|
if (seglen == 0) {
|
|
/* / */
|
|
if (!collapse_slashes) {
|
|
path[pathlen++] = '/';
|
|
}
|
|
} else if (seglen == 1 && addpath[0] == '.') {
|
|
/* ./ */
|
|
} else if (seglen == 2 && addpath[0] == '.' && addpath[1] == '.') {
|
|
/* backpath (../) */
|
|
if (pathlen == 1 && path[0] == '/') {
|
|
} else if (pathlen == 0
|
|
|| (pathlen == 3
|
|
&& !memcmp(path + pathlen - 3, "../", 3))
|
|
|| (pathlen > 3
|
|
&& !memcmp(path + pathlen - 4, "/../", 4))) {
|
|
/* Append another backpath, including
|
|
* trailing slash if present.
|
|
*/
|
|
memcpy(path + pathlen, "../", *next ? 3 : 2);
|
|
pathlen += *next ? 3 : 2;
|
|
} else {
|
|
/* otherwise crop the prior segment */
|
|
do {
|
|
--pathlen;
|
|
} while (pathlen && path[pathlen - 1] != '/');
|
|
}
|
|
} else {
|
|
/* An actual segment, append it to the destination path */
|
|
if (*next) {
|
|
seglen++;
|
|
}
|
|
memcpy(path + pathlen, addpath, seglen);
|
|
pathlen += seglen;
|
|
}
|
|
|
|
/* Skip over trailing slash to the next segment */
|
|
if (*next) {
|
|
++next;
|
|
}
|
|
|
|
addpath = next;
|
|
}
|
|
path[pathlen] = '\0';
|
|
return path;
|
|
}
|
|
|
|
std::string Util::normalizeDir(const std::string &dirname) {
|
|
string ret = Util::canonicalize(dirname);
|
|
if (!ret.empty() && ret[ret.length() - 1] != '/') {
|
|
ret += '/';
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::string Util::escapeStringForCPP(const char *input, int len,
|
|
bool* binary /* = NULL */) {
|
|
if (binary) *binary = false;
|
|
string ret;
|
|
ret.reserve((len << 1) + 2);
|
|
for (int i = 0; i < len; i++) {
|
|
unsigned char ch = input[i];
|
|
switch (ch) {
|
|
case '\n': ret += "\\n"; break;
|
|
case '\r': ret += "\\r"; break;
|
|
case '\t': ret += "\\t"; break;
|
|
case '\a': ret += "\\a"; break;
|
|
case '\b': ret += "\\b"; break;
|
|
case '\f': ret += "\\f"; break;
|
|
case '\v': ret += "\\v"; break;
|
|
case '\0': ret += "\\000"; if (binary) *binary = true; break;
|
|
case '\"': ret += "\\\""; break;
|
|
case '\\': ret += "\\\\"; break;
|
|
case '?': ret += "\\?"; break; // avoiding trigraph errors
|
|
default:
|
|
if (isprint(ch)) {
|
|
ret += ch;
|
|
} else {
|
|
// output in octal notation
|
|
char buf[10];
|
|
snprintf(buf, sizeof(buf), "\\%03o", ch);
|
|
ret += buf;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::string Util::escapeStringForPHP(const char *input, int len) {
|
|
string output;
|
|
output.reserve((len << 1) + 2);
|
|
output = "'";
|
|
for (int i = 0; i < len; i++) {
|
|
unsigned char ch = input[i];
|
|
switch (ch) {
|
|
case '\n': output += "'.\"\\n\".'"; break;
|
|
case '\r': output += "'.\"\\r\".'"; break;
|
|
case '\t': output += "'.\"\\t\".'"; break;
|
|
case '\'': output += "'.\"'\".'"; break;
|
|
case '\\': output += "'.\"\\\\\".'"; break;
|
|
default:
|
|
output += ch;
|
|
break;
|
|
}
|
|
}
|
|
output += "'";
|
|
replaceAll(output, ".''.", ".");
|
|
replaceAll(output, "''.", "");
|
|
replaceAll(output, ".''", "");
|
|
replaceAll(output, "\".\"", "");
|
|
return output;
|
|
}
|
|
|
|
const void *Util::buffer_duplicate(const void *src, int size) {
|
|
char *s = (char *)malloc(size + 1); // '\0' in the end
|
|
memcpy(s, src, size);
|
|
s[size] = '\0';
|
|
return s;
|
|
}
|
|
|
|
const void *Util::buffer_append(const void *buf1, int size1,
|
|
const void *buf2, int size2) {
|
|
char *s = (char *)realloc(const_cast<void *>(buf1), size1 + size2 + 1);
|
|
memcpy((char *)s + size1, buf2, size2);
|
|
s[size1 + size2] = '\0';
|
|
return s;
|
|
}
|
|
|
|
void Util::string_printf(std::string &msg, const char *fmt, ...) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
string_vsnprintf(msg, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void Util::string_vsnprintf(std::string &msg, const char *fmt, va_list ap) {
|
|
int i = 0;
|
|
for (int len = 1024; msg.empty(); len <<= 1) {
|
|
va_list v;
|
|
va_copy(v, ap);
|
|
|
|
char *buf = (char*)malloc(len);
|
|
if (vsnprintf(buf, len, fmt, v) < len) {
|
|
msg = buf;
|
|
}
|
|
free(buf);
|
|
|
|
va_end(v);
|
|
if (++i > 10) break;
|
|
}
|
|
}
|
|
|
|
void Util::find(std::vector<std::string> &out,
|
|
const std::string &root, const char *path, bool php,
|
|
const std::set<std::string> *excludeDirs /* = NULL */,
|
|
const std::set<std::string> *excludeFiles /* = NULL */) {
|
|
if (!path) path = "";
|
|
if (*path == '/') path++;
|
|
|
|
string spath = path;
|
|
if (spath.length() && spath[spath.length() - 1] != '/') {
|
|
spath += '/';
|
|
}
|
|
if (excludeDirs && excludeDirs->find(spath) != excludeDirs->end()) {
|
|
return;
|
|
}
|
|
|
|
string fullPath = root + path;
|
|
if (fullPath.empty()) {
|
|
return;
|
|
}
|
|
DIR *dir = opendir(fullPath.c_str());
|
|
if (dir == nullptr) {
|
|
Logger::Error("Util::find(): unable to open directory %s",
|
|
fullPath.c_str());
|
|
return;
|
|
}
|
|
if (fullPath[fullPath.length() - 1] != '/') {
|
|
fullPath += '/';
|
|
}
|
|
|
|
dirent *e;
|
|
while ((e = readdir(dir))) {
|
|
char *ename = e->d_name;
|
|
|
|
// skipping . .. hidden files
|
|
if (ename[0] == '.' || !*ename) {
|
|
continue;
|
|
}
|
|
string fe = fullPath + ename;
|
|
struct stat se;
|
|
if (stat(fe.c_str(), &se)) {
|
|
Logger::Error("Util::find(): unable to stat %s", fe.c_str());
|
|
continue;
|
|
}
|
|
|
|
if ((se.st_mode & S_IFMT) == S_IFDIR) {
|
|
string subdir = spath + ename;
|
|
find(out, root, subdir.c_str(), php, excludeDirs, excludeFiles);
|
|
continue;
|
|
}
|
|
|
|
// skipping "tags" files
|
|
if (strcmp(ename, "tags") == 0) {
|
|
continue;
|
|
}
|
|
|
|
// skipping emacs leftovers
|
|
char last = ename[strlen(ename) - 1];
|
|
if (last == '~' || last == '#') {
|
|
continue;
|
|
}
|
|
|
|
bool isPHP = false;
|
|
const char *p = strrchr(ename, '.');
|
|
if (p) {
|
|
isPHP = (strncmp(p + 1, "php", 3) == 0);
|
|
} else {
|
|
try {
|
|
string line;
|
|
std::ifstream fin(fe.c_str());
|
|
if (std::getline(fin, line)) {
|
|
if (line[0] == '#' && line[1] == '!' &&
|
|
line.find("php") != string::npos) {
|
|
isPHP = true;
|
|
}
|
|
}
|
|
} catch (...) {
|
|
Logger::Error("Util::find(): unable to read %s", fe.c_str());
|
|
}
|
|
}
|
|
|
|
if (isPHP == php &&
|
|
(!excludeFiles ||
|
|
excludeFiles->find(spath + ename) == excludeFiles->end())) {
|
|
out.push_back(fe);
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
std::string Util::format_pattern(const std::string &pattern,
|
|
bool prefixSlash) {
|
|
if (pattern.empty()) return pattern;
|
|
|
|
std::string ret = "#";
|
|
for (unsigned int i = 0; i < pattern.size(); i++) {
|
|
char ch = pattern[i];
|
|
|
|
// apache rewrite rules don't require initial slash
|
|
if (prefixSlash && i == 0 && ch == '^') {
|
|
char ch1 = pattern[1];
|
|
if (ch1 != '/' && ch1 != '(') {
|
|
ret += "^/";
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ch == '#') {
|
|
ret += "\\#";
|
|
} else {
|
|
ret += ch;
|
|
}
|
|
}
|
|
ret += '#';
|
|
return ret;
|
|
}
|
|
|
|
char* Util::getNativeFunctionName(void* codeAddr) {
|
|
void* buf[1] = {codeAddr};
|
|
char** symbols = backtrace_symbols(buf, 1);
|
|
char* functionName = nullptr;
|
|
if (symbols != nullptr) {
|
|
//
|
|
// the output from backtrace_symbols looks like this:
|
|
// ../path/hhvm/hhvm(_ZN4HPHP2VM6Transl17interpOneIterInitEv+0) [0x17cebe9]
|
|
//
|
|
// we first want to extract the mangled name from it to get this:
|
|
// _ZN4HPHP2VM6Transl17interpOneIterInitEv
|
|
//
|
|
// and then pass this to abi::__cxa_demangle to get the demanged name:
|
|
// HPHP::Transl::interpOneIterInit()
|
|
//
|
|
// Sometimes, though, backtrace_symbols can't find the function name
|
|
// and ends up giving us a blank managled name, like this:
|
|
// ../path/hhvm/hhvm() [0x17e4d01]
|
|
// or this: [0x7fffca800130]
|
|
//
|
|
char* start = strchr(*symbols, '(');
|
|
if (start) {
|
|
start++;
|
|
char* end = strchr(start, '+');
|
|
if (end != nullptr) {
|
|
size_t len = end-start;
|
|
functionName = new char[len+1];
|
|
strncpy(functionName, start, len);
|
|
functionName[len] = '\0';
|
|
int status;
|
|
char* demangledName = abi::__cxa_demangle(functionName, 0, 0, &status);
|
|
if (status == 0) {
|
|
delete []functionName;
|
|
functionName = demangledName;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
free(symbols);
|
|
if (functionName == nullptr) {
|
|
#define MAX_ADDR_HEX_LEN 40
|
|
functionName = new char[MAX_ADDR_HEX_LEN + 3];
|
|
sprintf(functionName, "%40p", codeAddr);
|
|
}
|
|
return functionName;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|