1/**
2 * @file oprof_start_util.cpp
3 * Miscellaneous helpers for the GUI start
4 *
5 * @remark Copyright 2002 OProfile authors
6 * @remark Read the file COPYING
7 *
8 * @author Philippe Elie
9 * @author John Levon
10 */
11
12#include <dirent.h>
13#include <unistd.h>
14#include <glob.h>
15
16#include <cerrno>
17#include <vector>
18#include <cmath>
19#include <sstream>
20#include <iostream>
21#include <fstream>
22#include <cstdlib>
23
24#include <qfiledialog.h>
25#include <qmessagebox.h>
26
27#include "op_file.h"
28#include "file_manip.h"
29#include "child_reader.h"
30#include "op_libiberty.h"
31
32#include "oprof_start.h"
33#include "oprof_start_util.h"
34
35using namespace std;
36
37namespace {
38
39// return the ~ expansion suffixed with a '/'
40string const get_config_dir()
41{
42	return "/root";
43}
44
45string daemon_pid;
46
47} // namespace anon
48
49daemon_status::daemon_status()
50	: running(false),
51	  nr_interrupts(0)
52{
53	int HZ;
54	if (!daemon_pid.empty()) {
55		string proc_filename = string("/proc/") + daemon_pid + "/exe";
56		string const exec = op_realpath(proc_filename);
57		if (exec == proc_filename)
58			daemon_pid.erase();
59		else
60			running = true;
61	}
62
63	if (daemon_pid.empty()) {
64		DIR * dir;
65		struct dirent * dirent;
66
67		if (!(dir = opendir("/proc"))) {
68			perror("oprofiled: /proc directory could not be opened. ");
69			exit(EXIT_FAILURE);
70		}
71
72		while ((dirent = readdir(dir))) {
73			string const exec =
74				op_realpath(string("/proc/")
75				               + dirent->d_name + "/exe");
76			string const name = op_basename(exec);
77			if (name != "oprofiled")
78				continue;
79
80			daemon_pid = dirent->d_name;
81			running = true;
82		}
83
84		closedir(dir);
85	}
86
87	HZ = sysconf(_SC_CLK_TCK);
88	if (HZ == -1) {
89		perror("oprofiled: Unable to determine clock ticks per second. ");
90		exit(EXIT_FAILURE);
91	}
92
93	if (daemon_pid.empty())
94		return;
95
96	nr_interrupts = 0;
97
98	switch (op_get_interface()) {
99	case OP_INTERFACE_24:
100		{
101			ifstream ifs3("/proc/sys/dev/oprofile/nr_interrupts");
102			if (ifs3)
103				ifs3 >> nr_interrupts;
104		}
105		break;
106	case OP_INTERFACE_26:
107		{
108			static unsigned int old_sum_interrupts;
109			unsigned int sum_interrupts = 0;
110			glob_t file_names;
111
112			file_names.gl_offs = 0;
113			glob("/dev/oprofile/stats/cpu*/sample_received",
114			     GLOB_DOOFFS, NULL, &file_names);
115
116			for (size_t i = 0; i < file_names.gl_pathc; ++i) {
117				ifstream ifs3(file_names.gl_pathv[i]);
118				if (ifs3) {
119					unsigned int file_interrupts;
120					ifs3 >> file_interrupts;
121					sum_interrupts += file_interrupts;
122				}
123			}
124			if (old_sum_interrupts > sum_interrupts)
125				// occur if we stop/restart daemon.
126				old_sum_interrupts = 0;
127			nr_interrupts = sum_interrupts - old_sum_interrupts;
128			old_sum_interrupts = sum_interrupts;
129			globfree(&file_names);
130		}
131		break;
132	default:
133		break;
134	}
135}
136
137
138/**
139 * get_config_filename - get absolute filename of file in user $HOME
140 * @param filename  the relative filename
141 *
142 * Get the absolute path of a file in a user's home directory.
143 */
144string const get_config_filename(string const & filename)
145{
146	return get_config_dir() + "/" + filename;
147}
148
149
150/**
151 * check_and_create_config_dir - make sure config dir is accessible
152 *
153 * Returns %true if the dir is accessible.
154 */
155bool check_and_create_config_dir()
156{
157	string dir = get_config_filename(".oprofile");
158
159	char * name = xstrdup(dir.c_str());
160
161	if (create_dir(name)) {
162		ostringstream out;
163		out << "unable to create " << dir << " directory ";
164		out << "cause: " << strerror(errno);
165		QMessageBox::warning(0, 0, out.str().c_str());
166
167		free(name);
168
169		return false;
170	}
171
172	free(name);
173	return true;
174}
175
176
177/**
178 * format - re-format a string
179 * @param orig  string to format
180 * @param maxlen  width of line
181 *
182 * Re-formats a string to fit into a certain width,
183 * breaking lines at spaces between words.
184 *
185 * Returns the formatted string
186 */
187string const format(string const & orig, uint const maxlen)
188{
189	string text(orig);
190
191	istringstream ss(text);
192	vector<string> lines;
193
194	string oline;
195	string line;
196
197	while (getline(ss, oline)) {
198		if (line.size() + oline.size() < maxlen) {
199			lines.push_back(line + oline);
200			line.erase();
201		} else {
202			lines.push_back(line);
203			line.erase();
204			string s;
205			string word;
206			istringstream oss(oline);
207			while (oss >> word) {
208				if (line.size() + word.size() > maxlen) {
209					lines.push_back(line);
210					line.erase();
211				}
212				line += word + " ";
213			}
214		}
215	}
216
217	if (line.size())
218		lines.push_back(line);
219
220	string ret;
221
222	for(vector<string>::const_iterator it = lines.begin(); it != lines.end(); ++it)
223		ret += *it + "\n";
224
225	return ret;
226}
227
228
229/**
230 * do_exec_command - execute a command
231 * @param cmd  command name
232 * @param args  arguments to command
233 *
234 * Execute a command synchronously. An error message is shown
235 * if the command returns a non-zero status, which is also returned.
236 *
237 * The arguments are verified and will refuse to execute if they contain
238 * shell metacharacters.
239 */
240int do_exec_command(string const & cmd, vector<string> const & args)
241{
242	ostringstream err;
243	bool ok = true;
244
245	// verify arguments
246	for (vector<string>::const_iterator cit = args.begin();
247		cit != args.end(); ++cit) {
248		if (verify_argument(*cit))
249			continue;
250
251		QMessageBox::warning(0, 0,
252			string(
253			"Could not execute: Argument \"" + *cit +
254			"\" contains shell metacharacters.\n").c_str());
255		return EINVAL;
256	}
257
258	child_reader reader(cmd, args);
259	if (reader.error())
260		ok = false;
261
262	if (ok)
263		reader.get_data(cout, err);
264
265	int ret = reader.terminate_process();
266	if (ret) {
267		string error = reader.error_str() + "\n";
268		error += "Failed: \n" + err.str() + "\n";
269		string cmdline = cmd;
270		for (vector<string>::const_iterator cit = args.begin();
271		     cit != args.end(); ++cit) {
272			cmdline += " " + *cit + " ";
273		}
274		error += "\n\nCommand was :\n\n" + cmdline + "\n";
275
276		QMessageBox::warning(0, 0, format(error, 50).c_str());
277	}
278
279	return ret;
280}
281
282
283/**
284 * do_open_file_or_dir - open file/directory
285 * @param base_dir  directory to start at
286 * @param dir_only  directory or filename to select
287 *
288 * Select a file or directory. The selection is returned;
289 * an empty string if the selection was cancelled.
290 */
291string const do_open_file_or_dir(string const & base_dir, bool dir_only)
292{
293	QString result;
294
295	if (dir_only) {
296		result = QFileDialog::getExistingDirectory(base_dir.c_str(), 0,
297			"open_file_or_dir", "Get directory name", true);
298	} else {
299		result = QFileDialog::getOpenFileName(base_dir.c_str(), 0, 0,
300			"open_file_or_dir", "Get filename");
301	}
302
303	if (result.isNull())
304		return string();
305	else
306		return result.latin1();
307}
308
309/**
310 * verify_argument - check string for potentially dangerous characters
311 *
312 * This function returns false if the string contains dangerous shell
313 * metacharacters.
314 *
315 * WWW Security FAQ dangerous chars:
316 *
317 * & ; ` ' \ " | * ? ~ < > ^ ( ) [ ] { } $ \n \r
318 *
319 * David Wheeler: ! #
320 *
321 * We allow '-' because we disallow whitespace. We allow ':' and '='
322 */
323bool verify_argument(string const & str)
324{
325	if (str.find_first_not_of(
326		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
327		"abcdefghijklmnopqrstuvwxyz0123456789_:=-+%,./")
328		!= string::npos)
329		return false;
330	return true;
331}
332