ede/pekwm/Util.cc

655 lines
16 KiB
C++

//
// Util.cc for pekwm
// Copyright © 2002-2009 Claes Nästén <me@pekdon.net>
//
// misc.cc for aewm++
// Copyright (C) 2000 Frank Hale <frankhale@yahoo.com>
// http://sapphire.sourceforge.net/
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#include <cerrno>
#include <iostream>
#include <sstream>
#include <fstream>
#include <list>
#include <cstdio>
#include <cstring>
#include <cwchar>
extern "C" {
#include <iconv.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#include <errno.h>
}
#include "Util.hh"
using std::cerr;
using std::endl;
using std::ostringstream;
using std::string;
using std::transform;
using std::vector;
using std::wstring;
using std::list;
using std::ifstream;
using std::ofstream;
using std::find;
using std::map;
using std::getenv;
using std::wcstombs;
using std::wmemset;
using std::mbstowcs;
using std::exit;
namespace Util {
#ifndef HOST_NAME_MAX
#define HOST_NAME_MAX 255
#endif // HOST_NAME_MAX
static iconv_t do_iconv_open(const char **from_names, const char **to_names);
static size_t do_iconv (iconv_t ic, const char **inp, size_t *in_bytes,
char **outp, size_t *out_bytes);
// Initializers, members used for shared buffer
unsigned int WIDE_STRING_COUNT = 0;
iconv_t IC_TO_WC = reinterpret_cast<iconv_t>(-1);
iconv_t IC_TO_UTF8 = reinterpret_cast<iconv_t>(-1);
char *ICONV_BUF = 0;
size_t ICONV_BUF_LEN = 0;
// Constants, name of iconv internal names
const char *ICONV_WC_NAMES[] = {"WCHAR_T", "UCS-4", 0};
const char *ICONV_UTF8_NAMES[] = {"UTF-8", "UTF8", 0};
const char *ICONV_UTF8_INVALID_STR = "<INVALID>";
const wchar_t *ICONV_WIDE_INVALID_STR = L"<INVALID>";
// Constants, maximum number of bytes a single UTF-8 character can use.
const size_t UTF8_MAX_BYTES = 6;
//! @brief Fork and execute command with /bin/sh and execlp
void
forkExec(std::string command)
{
if (command.length() == 0) {
#ifdef DEBUG
cerr << __FILE__ << "@" << __LINE__ << ": "
<< "Util::forkExec() *** command length == 0" << endl;
#endif // DEBUG
return;
}
pid_t pid = fork();
switch (pid) {
case 0:
setsid();
execlp("/bin/sh", "sh", "-c", command.c_str(), (char *) 0);
cerr << __FILE__ << "@" << __LINE__ << ": "
<< "Util::forkExec(" << command << ") execlp failed." << endl;
exit(1);
case -1:
cerr << __FILE__ << "@" << __LINE__ << ": "
<< "Util::forkExec(" << command << ") fork failed." << endl;
}
}
/**
* Wrapper for gethostname returning a string instead of populating
* char buffer.
*/
std::string
getHostname(void)
{
string hostname;
// Set WM_CLIENT_MACHINE
char hostname_buf[HOST_NAME_MAX + 1];
if (! gethostname(hostname_buf, HOST_NAME_MAX)) {
// Make sure it is null terminated
hostname_buf[HOST_NAME_MAX] = '\0';
hostname = hostname_buf;
}
return hostname;
}
//! @brief Determines if the file exists
bool
isFile(const std::string &file)
{
if (file.size() == 0) {
return false;
}
struct stat stat_buf;
if (stat(file.c_str(), &stat_buf) == 0) {
return (S_ISREG(stat_buf.st_mode));
}
return false;
}
//! @brief Determines if the file is executable for the current user.
bool
isExecutable(const std::string &file)
{
if (file.size() == 0) {
#ifdef DEBUG
cerr << __FILE__ << "@" << __LINE__ << ": "
<< "Util::isExecutable() *** file length == 0" << endl;
#endif // DEBUG
return false;
}
struct stat stat_buf;
if (! stat(file.c_str(), &stat_buf)) {
if (stat_buf.st_uid == getuid()) { // user readable and executable
if ((stat_buf.st_mode&S_IRUSR) && (stat_buf.st_mode&S_IXUSR)) {
return true;
}
}
if (getgid() == stat_buf.st_gid) { // group readable and executable
if ((stat_buf.st_mode&S_IRGRP) && (stat_buf.st_mode&S_IXGRP)) {
return true;
}
}
if ((stat_buf.st_mode&S_IROTH) && (stat_buf.st_mode&S_IXOTH)) {
return true; // other readable and executable
}
}
return false;
}
/**
* Get file mtime.
*/
time_t
getMtime(const std::string &file)
{
struct stat stat_buf;
if (! stat(file.c_str(), &stat_buf)) {
return stat_buf.st_mtime;
} else {
return 0;
}
}
/**
* Check if file has different mtime than provided mtime.
*/
bool
isFileChanged(const std::string &file, time_t &mtime)
{
time_t cur_mtime = getMtime(file);
if (cur_mtime != mtime) {
mtime = cur_mtime;
return true;
}
return false;
}
/**
* Check if old_file needs to be reloaded due to it being different
* from new_file, path or mtime.
*/
bool
requireReload(std::map <std::string, time_t> &state, const std::string &file)
{
// Check for the file, signal reload if not previously loaded.
map<string, time_t>::iterator it(state.find(file));
if (it == state.end()) {
return true;
}
// Check state of all files, if one is updated reload.
for (it = state.begin(); it != state.end(); ++it) {
if (isFileChanged(it->first, it->second)) {
return true;
}
}
return false;
}
/**
* Copies a single text file.
*/
bool
copyTextFile(const std::string &from, const std::string &to)
{
if ((from.length() == 0) || (to.length() == 0)) {
return false;
}
ifstream stream_from(from.c_str());
if (! stream_from.good()) {
cerr << __FILE__ << "@" << __LINE__ << ": "
<< "Can't copy: " << from << " to: " << to << endl;
return false;
}
ofstream stream_to(to.c_str());
if (! stream_to.good()) {
cerr << __FILE__ << "@" << __LINE__ << ": "
<< "Can't copy: " << from << " to: " << to << endl;
return false;
}
stream_to << stream_from.rdbuf();
return true;
}
/**
* Get name of the current user.
*/
string
getUserName(void)
{
// Try to lookup current user with
struct passwd *entry = getpwuid(geteuid());
if (entry && entry->pw_name) {
return entry->pw_name;
} else {
if (getenv("USER")) {
return getenv("USER");
} else {
return "UNKNOWN";
}
}
}
//! @brief Returns .extension of file
std::string
getFileExt(const std::string &file)
{
string::size_type pos = file.find_last_of('.');
if ((pos != string::npos) && (pos < file.size())) {
return file.substr(pos + 1, file.size() - pos - 1);
} else {
return string("");
}
}
//! @brief Returns dir part of file
std::string
getDir(const std::string &file)
{
string::size_type pos = file.find_last_of('/');
if ((pos != string::npos) && (pos < file.size())) {
return file.substr(0, pos);
} else {
return string("");
}
}
//! @brief Replaces the ~ with the complete homedir path.
void
expandFileName(std::string &file)
{
if (file.size() > 0) {
if (file[0] == '~') {
file.replace(0, 1, getenv("HOME"));
}
}
}
//! @brief Split the string str based on separator sep and put into vals
//!
//! This splits the string str into to max_tokens parts and puts in the vector
//! vals. If max is 0 then it'll split it into as many tokens
//! as possible, max defaults to 0.
//! splitString returns the number of tokens it put into vals.
//!
//! @param str String to split
//! @param vals Vector to put split values into
//! @param sep Separators to use when splitting string
//! @param max Maximum number of elements to put into vals (optional)
//! @param include_empty Include empty elements, defaults to false.
//! @return Number of tokens inserted into vals
uint
splitString(const std::string str, std::vector<std::string> &toks, const char *sep, uint max, bool include_empty)
{
if (str.size() < 1) {
return 0;
}
uint n = toks.size();
string::size_type s, e;
s = str.find_first_not_of(" \t\n");
for (uint i = 0; ((i < max) || (max == 0)) && (s != string::npos); ++i) {
e = str.find_first_of(sep, s);
if ((e != string::npos) && (i < (max - 1))) {
toks.push_back(str.substr(s, e - s));
} else if (s < str.size()) {
toks.push_back(str.substr(s, str.size() - s));
break;
} else {
break;
}
if (include_empty) {
s = str.find_first_of(sep, e + 1);
if (s != (e + 1)) {
s = str.find_first_not_of(sep, e);
}
} else {
s = str.find_first_not_of(sep, e);
}
}
return (toks.size() - n);
}
//! @brief Converts wide-character string to multibyte version
//! @param str String to convert.
//! @return Returns multibyte version of string.
std::string
to_mb_str(const std::wstring &str)
{
size_t ret, num = str.size() * 6 + 1;
char *buf = new char[num];
memset(buf, '\0', num);
ret = wcstombs(buf, str.c_str(), num);
if (ret == static_cast<size_t>(-1)) {
cerr << " *** WARNING: failed to convert wide string to multibyte string" << endl;
}
string ret_str(buf);
delete [] buf;
return ret_str;
}
//! @brief Converts multibyte string to wide-character version
//! @param str String to convert.
//! @return Returns wide-character version of string.
std::wstring
to_wide_str(const std::string &str)
{
size_t ret, num = str.size() + 1;
wchar_t *buf = new wchar_t[num];
wmemset(buf, L'\0', num);
ret = mbstowcs(buf, str.c_str(), num);
if (ret == static_cast<size_t>(-1)) {
cerr << " *** WARNING: failed to convert multibyte string to wide string" << endl;
}
wstring ret_str(buf);
delete [] buf;
return ret_str;
}
//! @brief Open iconv handle with to/from names.
//! @param from_names null terminated list of from name alternatives.
//! @param to_names null terminated list of to name alternatives.
//! @return iconv_t handle on success, else -1.
iconv_t
do_iconv_open(const char **from_names, const char **to_names)
{
iconv_t ic = reinterpret_cast<iconv_t>(-1);
// Try all combinations of from/to name's to get a working
// conversion handle.
for (unsigned int i = 0; from_names[i]; ++i) {
for (unsigned int j = 0; to_names[j]; ++j) {
ic = iconv_open (to_names[j], from_names[i]);
if (ic != reinterpret_cast<iconv_t>(-1)) {
#ifdef HAVE_ICONVCTL
int int_value_one = 1;
iconvctl(ic, ICONV_SET_DISCARD_ILSEQ, &int_value_one);
#endif // HAVE_ICONVCTL
return ic;
}
}
}
return ic;
}
//! @brief Iconv wrapper to hide different definitions of iconv.
//! @param ic iconv handle.
//! @param inp Input pointer.
//! @param in_bytes Input bytes.
//! @param outp Output pointer.
//! @param out_bytes Output bytes.
//! @return number of bytes converted irreversible or -1 on error.
size_t
do_iconv (iconv_t ic, const char **inp, size_t *in_bytes,
char **outp, size_t *out_bytes)
{
#ifdef ICONV_CONST
return iconv(ic, inp, in_bytes, outp, out_bytes);
#else // !ICONV_CONST
# ifdef __minix
// this is as MESS across OS-es...
return iconv(ic, (const char**)inp, in_bytes, outp, out_bytes);
# else
return iconv(ic, const_cast<char**>(inp), in_bytes, outp, out_bytes);
# endif
#endif // ICONV_CONST
}
//! @brief Init iconv conversion.
void
iconv_init (void)
{
// Cleanup previous init if any, being paranoid.
iconv_deinit ();
// Raise exception if this fails
IC_TO_WC = do_iconv_open (ICONV_UTF8_NAMES, ICONV_WC_NAMES);
IC_TO_UTF8 = do_iconv_open (ICONV_WC_NAMES, ICONV_UTF8_NAMES);
// Equal mean
if (IC_TO_WC != reinterpret_cast<iconv_t>(-1)
&& IC_TO_UTF8 != reinterpret_cast<iconv_t>(-1)) {
// Create shared buffer.
ICONV_BUF_LEN = 1024;
ICONV_BUF = new char[ICONV_BUF_LEN];
}
}
//! @brief Deinit iconv conversion.
void
iconv_deinit (void)
{
// Cleanup resources
if (IC_TO_WC != reinterpret_cast<iconv_t>(-1)) {
iconv_close (IC_TO_WC);
}
if (IC_TO_UTF8 != reinterpret_cast<iconv_t>(-1)) {
iconv_close (IC_TO_UTF8);
}
if (ICONV_BUF) {
delete [] ICONV_BUF;
}
// Set members to safe values
IC_TO_WC = reinterpret_cast<iconv_t>(-1);
IC_TO_UTF8 = reinterpret_cast<iconv_t>(-1);
ICONV_BUF = 0;
ICONV_BUF_LEN = 0;
}
//! @brief Validate buffer size, grow if required.
//! @param size Required size.
void
iconv_buf_grow (size_t size)
{
if (ICONV_BUF_LEN < size) {
// Free resources, if any.
if (ICONV_BUF) {
delete [] ICONV_BUF;
}
// Calculate new buffer length and allocate new buffer
for (; ICONV_BUF_LEN < size; ICONV_BUF_LEN *= 2)
;
ICONV_BUF = new char[ICONV_BUF_LEN];
}
}
//! @brief Converts wide-character string to UTF-8
//! @param str String to convert.
//! @return Returns UTF-8 representation of string.
std::string
to_utf8_str(const std::wstring &str)
{
string utf8_str;
// Calculate length
size_t in_bytes = str.size() * sizeof (wchar_t);
size_t out_bytes = str.size() * UTF8_MAX_BYTES + 1;
iconv_buf_grow(out_bytes);
// Convert
const char *inp = reinterpret_cast<const char*>(str.c_str());
char *outp = ICONV_BUF;
size_t len = do_iconv (IC_TO_UTF8, &inp, &in_bytes, &outp, &out_bytes);
if (len != static_cast<size_t>(-1)) {
// Terminate string and cache result
*outp = '\0';
utf8_str = ICONV_BUF;
} else {
cerr << " *** WARNING: to_utf8_str, failed with error "
<< strerror (errno) << endl;
utf8_str = ICONV_UTF8_INVALID_STR;
}
return utf8_str;
}
//! @brief Converts to wide string from UTF-8
//! @param str String to convert.
//! @return Returns wide representation of string.
std::wstring
from_utf8_str(const std::string &str)
{
wstring wide_str;
// Calculate length
size_t in_bytes = str.size();
size_t out_bytes = (in_bytes + 1) * sizeof(wchar_t);
iconv_buf_grow(out_bytes);
// Convert
const char *inp = str.c_str();
char *outp = ICONV_BUF;
size_t len = do_iconv(IC_TO_WC, &inp, &in_bytes, &outp, &out_bytes);
if (len != static_cast<size_t>(-1)) {
// Terminate string and cache result
*reinterpret_cast<wchar_t*>(outp) = L'\0';
wide_str = reinterpret_cast<wchar_t*>(ICONV_BUF);
} else {
cerr << " *** WARNING: from_utf8_str, failed on string \""
<< str << "\"." << endl;
wide_str = ICONV_WIDE_INVALID_STR;
}
return wide_str;
}
/**
* Add object to list making sure there are no duplicates.
*/
void
file_backed_list::push_back_unique(const std::wstring &entry)
{
list<wstring>::iterator it(find(begin(), end(), entry));
if (it != end()) {
erase(it);
}
push_back(entry);
}
/**
* Load list from file, updating _path if set.
*
* @param path Load data from path.
* @return Number of elements loaded.
*/
unsigned int
file_backed_list::load (const std::string &path)
{
unsigned int loaded = 0;
ifstream ifile;
ifile.open(path.c_str());
if (ifile.is_open()) {
// Update only path if successfully opened.
_path = path;
string mb_line;
while (ifile.good()) {
getline(ifile, mb_line);
if (mb_line.size()) {
push_back(to_wide_str(mb_line));
++loaded;
}
}
ifile.close();
}
return loaded;
}
/**
* Save list from file overwriting previous data.
*/
bool
file_backed_list::save (const std::string &path)
{
bool status = false;
ofstream ofile(path.c_str());
if (ofile.is_open()) {
// Update path if successfully opened.
_path = path;
string line;
list<wstring>::iterator it(begin ());
for (; it != end(); ++it) {
ofile << to_utf8_str(*it) << "\n";
}
ofile.close();
status = true;
}
return status;
}
} // end namespace Util.