Dunst
Lightweight notification daemon
Loading...
Searching...
No Matches
utils.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: BSD-3-Clause */
8
9#include <assert.h>
10#include <ctype.h>
11#include <errno.h>
12#include <glib.h>
13#include <pwd.h>
14#include <stdbool.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <sys/types.h>
18#include <sys/stat.h>
19#include <time.h>
20#include <unistd.h>
21#include <wordexp.h>
22
23#include "utils.h"
24#include "log.h"
25#include "settings_data.h"
26
27char *string_replace_char(char needle, char replacement, char *haystack)
28{
29 ASSERT_OR_RET(haystack, NULL);
30
31 char *current = haystack;
32 while ((current = strchr(current, needle)))
33 *current++ = replacement;
34 return haystack;
35}
36
37char *string_replace_at(char *buf, int pos, int len, const char *repl)
38{
39 assert(buf);
40 assert(repl);
41
42 char *tmp;
43 int size, buf_len, repl_len;
44
45 buf_len = strlen(buf);
46 repl_len = strlen(repl);
47 size = (buf_len - len) + repl_len + 1;
48
49 if (repl_len <= len) {
50 tmp = buf;
51 } else {
52 tmp = g_malloc(size);
53 memcpy(tmp, buf, pos);
54 }
55
56 memcpy(tmp + pos, repl, repl_len);
57 memmove(tmp + pos + repl_len, buf + pos + len, buf_len - (pos + len) + 1);
58
59 if (tmp != buf) {
60 g_free(buf);
61 }
62
63 return tmp;
64}
65
66char *string_replace_all(const char *needle, const char *replacement, char *haystack)
67{
68 ASSERT_OR_RET(haystack, NULL);
69 assert(needle);
70 assert(replacement);
71
72 char *start;
73 int needle_pos;
74 int needle_len, repl_len;
75
76 needle_len = strlen(needle);
77 if (needle_len == 0) {
78 return haystack;
79 }
80
81 start = strstr(haystack, needle);
82 repl_len = strlen(replacement);
83
84 while (start) {
85 needle_pos = start - haystack;
86 haystack = string_replace_at(haystack, needle_pos, needle_len, replacement);
87 start = strstr(haystack + needle_pos + repl_len, needle);
88 }
89 return haystack;
90}
91
92char *string_append(char *a, const char *b, const char *sep)
93{
94 if (STR_EMPTY(a)) {
95 g_free(a);
96 return g_strdup(b);
97 }
98 if (STR_EMPTY(b))
99 return a;
100
101 char *new;
102 if (!sep)
103 new = g_strconcat(a, b, NULL);
104 else
105 new = g_strconcat(a, sep, b, NULL);
106 g_free(a);
107
108 return new;
109}
110
111char *string_strip_quotes(const char *value)
112{
113 ASSERT_OR_RET(value, NULL);
114
115 size_t len = strlen(value);
116 char *s;
117
118 if (value[0] == '"' && value[len-1] == '"')
119 s = g_strndup(value + 1, len-2);
120 else
121 s = g_strdup(value);
122
123 return s;
124}
125
126void string_strip_delimited(char *str, char a, char b)
127{
128 assert(str);
129
130 int iread=-1, iwrite=0, copen=0;
131 int cskip = 0;
132 while (str[++iread] != 0) {
133 if (str[iread] == a) {
134 ++copen;
135 } else if (str[iread] == b && copen > 0) {
136 --copen;
137 } else if (copen == 0) {
138 cskip = 0;
139 str[iwrite++] = str[iread];
140 }
141 if (copen > 0) {
142 cskip++;
143 }
144 }
145 if (copen > 0) {
146 iread -= cskip;
147 for (int i = 0; i < cskip; i++) {
148 str[iwrite++] = str[iread++];
149 }
150 }
151 str[iwrite] = 0;
152}
153
154char **string_to_array(const char *string, const char *delimiter)
155{
156 char **arr = NULL;
157 if (string) {
158 arr = g_strsplit(string, delimiter, 0);
159 for (int i = 0; arr[i]; i++) {
160 g_strstrip(arr[i]);
161 }
162 }
163 return arr;
164}
165
167{
168 if (!s)
169 return -1;
170
171 int len = 0;
172 while (s[len])
173 len++;
174
175 return len;
176}
177
178char *string_to_path(char *string)
179{
180 ASSERT_OR_RET(string, string);
181
182 wordexp_t we;
183 switch (wordexp(string, &we, WRDE_NOCMD | WRDE_UNDEF)) {
184 case 0:
185 break;
186 case WRDE_BADCHAR:
187 LOG_W("Expansion of \"%s\" failed. It contains invalid characters.", string);
188 return string;
189 case WRDE_BADVAL:
190 LOG_W("Expansion of \"%s\" failed. It contains an undefined variable.", string);
191 return string;
192 case WRDE_CMDSUB:
193 LOG_W("Expansion of \"%s\" failed. The requested command substitution is currently not supported.", string);
194 return string;
195 case WRDE_NOSPACE:
196 LOG_W("Expansion of \"%s\" failed. We ran out of memory.", string);
197 return string;
198 case WRDE_SYNTAX:
199 LOG_W("Expansion of \"%s\" failed. It contains invalid syntax.", string);
200 return string;
201 }
202 g_free(string);
203
204 char *res = g_strjoinv(" ", we.we_wordv);
205 wordfree(&we);
206
207 return res;
208}
209
210bool safe_string_to_long_long(long long *in, const char *str) {
211 errno = 0;
212 char *endptr;
213 long long val = g_ascii_strtoll(str, &endptr, 10);
214
215 if (errno != 0) {
216 LOG_W("'%s': %s.", str, strerror(errno));
217 return false;
218 } else if (str == endptr) {
219 LOG_W("'%s': No digits found.", str);
220 return false;
221 } else if (*endptr != '\0') {
222 LOG_W("'%s': String contains non-digits.", str);
223 return false;
224 }
225 *in = val;
226 return true;
227}
228
229bool safe_string_to_int(int *in, const char *str) {
230 long long l;
231 if (!safe_string_to_long_long(&l, str))
232 return false;
233
234 // Check if it's in int range
235 if (l < INT_MIN || l > INT_MAX) {
236 errno = ERANGE;
237 LOG_W("'%s': %s.", str, strerror(errno));
238 return false;
239 }
240
241 *in = (int) l;
242 return true;
243}
244
245bool safe_string_to_double(double *in, const char *str) {
246 errno = 0;
247 char *endptr;
248 double val = g_ascii_strtod(str, &endptr);
249 if (errno != 0) {
250 LOG_W("'%s': %s.", str, strerror(errno));
251 return false;
252 } else if (str == endptr) {
253 LOG_W("'%s': No digits found.", str);
254 return false;
255 } else if (*endptr != '\0') {
256 LOG_W("'%s': String contains non-digits.", str);
257 return false;
258 }
259 *in = val;
260 return true;
261}
262
263gint64 string_to_time(const char *string)
264{
265 assert(string);
266
267 errno = 0;
268 char *endptr;
269 gint64 val = strtol(string, &endptr, 10);
270
271 if (errno != 0) {
272 LOG_W("Time: '%s': %s.", string, strerror(errno));
273 return 0;
274 } else if (string == endptr) {
275 errno = EINVAL;
276 LOG_W("Time: '%s': No digits found.", string);
277 return 0;
278 } else if (val < -1) {
279 // most times should not be negative, but show_age_threshhold
280 // can be -1
281 LOG_W("Time: '%s': Time should be positive (-1 is allowed too sometimes)",
282 string);
283 errno = EINVAL;
284 }
285 else if (errno == 0 && !*endptr) {
286 return S2US(val);
287 }
288 // endptr may point to a separating space
289 while (isspace(*endptr))
290 endptr++;
291
292 if (val < 0) {
293 LOG_W("Time: '%s' signal value -1 should not have a suffix", string);
294 errno = EINVAL;
295 return 0;
296 }
297
298 if (STRN_EQ(endptr, "ms", 2))
299 return val * 1000;
300 else if (STRN_EQ(endptr, "s", 1))
301 return S2US(val);
302 else if (STRN_EQ(endptr, "m", 1))
303 return S2US(val) * 60;
304 else if (STRN_EQ(endptr, "h", 1))
305 return S2US(val) * 60 * 60;
306 else if (STRN_EQ(endptr, "d", 1))
307 return S2US(val) * 60 * 60 * 24;
308 else
309 {
310 errno = EINVAL;
311 return 0;
312 }
313}
314
316{
317 struct timespec tv_now;
318
325#ifdef __linux__
326 clock_gettime(CLOCK_BOOTTIME, &tv_now);
327#else
328 clock_gettime(CLOCK_MONOTONIC, &tv_now);
329#endif
330 return S2US(tv_now.tv_sec) + tv_now.tv_nsec / 1000;
331}
332
333gint64 time_now(void)
334{
335 struct timespec tv_now;
336
337 clock_gettime(CLOCK_REALTIME, &tv_now);
338 return S2US(tv_now.tv_sec) + tv_now.tv_nsec / 1000;
339}
340
341gint64 modification_time(const char *path)
342{
343 struct stat statbuf;
344 if (stat(path, &statbuf) != 0)
345 return -1;
346
347 return S2US(statbuf.st_mtim.tv_sec) + statbuf.st_mtim.tv_nsec / 1000;
348}
349
350const char *user_get_home(void)
351{
352 static const char *home_directory = NULL;
353 ASSERT_OR_RET(!home_directory, home_directory);
354
355 // Check the HOME variable for the user's home
356 home_directory = getenv("HOME");
357 ASSERT_OR_RET(!home_directory, home_directory);
358
359 // Check the /etc/passwd entry for the user's home
360 home_directory = getpwuid(getuid())->pw_dir;
361
362 return home_directory;
363}
364
365bool safe_setenv(const char* key, const char* value){
366 if (!key)
367 return false;
368
369 if (!value)
370 setenv(key, "", 1);
371 else
372 setenv(key, value, 1);
373
374 return true;
375}
376
377// These sections are not interpreted as rules
378static const char* special_sections[] = {
379 "global",
380 "frame",
381 "experimental",
382 "shortcuts",
383 "urgency_low",
384 "urgency_normal",
385 "urgency_critical",
386};
387
388static const char* deprecated_sections[] = {
389 "frame",
390 "shortcuts",
391};
392
393static const char* deprecated_sections_message[] = {
394 "The settings from the frame section have been moved to the global section.", // frame
395 "Settings in the shortcuts sections have been moved to the global section.\nAlternatively you can bind shortcuts in your window manager to dunstctl commands. For that, see the manual for dunstctl.", // shortcuts
396};
397
398bool is_special_section(const char* s) {
399 for (size_t i = 0; i < G_N_ELEMENTS(special_sections); i++) {
400 if (STR_EQ(special_sections[i], s)) {
401 return true;
402 }
403 }
404 return false;
405}
406
407bool is_deprecated_section(const char* s) {
408 for (size_t i = 0; i < G_N_ELEMENTS(deprecated_sections); i++) {
409 if (STR_EQ(deprecated_sections[i], s)) {
410 return true;
411 }
412 }
413 return false;
414}
415
416const char *get_section_deprecation_message(const char *s) {
417 for (size_t i = 0; i < G_N_ELEMENTS(deprecated_sections); i++) {
418 if (STR_EQ(deprecated_sections[i], s)) {
419 return deprecated_sections_message[i];
420 }
421 }
422 return "";
423}
424
425char *string_strip_brackets(const char* s) {
426 if (!s)
427 return NULL;
428
429 size_t len = strlen(s);
430 if (s[0] == '(' && s[len-1] == ')')
431 return g_strndup(s + 1, len-2);
432 else
433 return NULL;
434
435}
436
437bool is_readable_file(const char * const path)
438{
439 struct stat statbuf;
440 bool result = false;
441
442 if (0 == stat(path, &statbuf)) {
444 if (!(statbuf.st_mode & (S_IFIFO | S_IFREG))) {
449 errno = EINVAL;
450 } else if (0 == access(path, R_OK)) { /* must also be readable */
451 result = true;
452 }
453 }
454
455 return result;
456}
457
458FILE *fopen_verbose(const char * const path)
459{
460 FILE *f = NULL;
461 char *real_path = string_to_path(g_strdup(path));
462
463 if (is_readable_file(real_path) && (f = fopen(real_path, "r")))
464 LOG_I(MSG_FOPEN_SUCCESS(path, f));
465 else
466 LOG_W(MSG_FOPEN_FAILURE(path));
467
468 g_free(real_path);
469 return f;
470}
471
472void add_paths_from_env(GPtrArray *arr, char *env_name, char *subdir, char *alternative) {
473 const char *xdg_data_dirs = g_getenv(env_name);
474 if (!xdg_data_dirs)
475 xdg_data_dirs = alternative;
476
477 char **xdg_data_dirs_arr = string_to_array(xdg_data_dirs, ":");
478 for (int i = 0; xdg_data_dirs_arr[i] != NULL; i++) {
479 char *loc = g_build_filename(xdg_data_dirs_arr[i], subdir, NULL);
480 g_ptr_array_add(arr, loc);
481 }
482 g_strfreev(xdg_data_dirs_arr);
483}
484
485bool string_is_int(const char *str) {
486 if (str != NULL) {
487 while (isspace(*str)) str++;
488 while (isdigit(*str)) str++;
489 while (isspace(*str)) str++;
490 return *str == '\0';
491 }
492 return true;
493}
494
495bool is_like_path(const char *string)
496{
497 return string[0] == '/' || string[0] == '~'
498 || (string[0] == '.' && string[1] == '.' && string[2] == '/')
499 || (string[0] == '.' && string[1] == '/');
500}
Logging subsystem and helpers.
List of all the valid settings and values.
gint64 modification_time(const char *path)
Get the modification time of the file at path.
Definition utils.c:341
gint64 time_monotonic_now(void)
Get the current monotonic time.
Definition utils.c:315
bool safe_setenv(const char *key, const char *value)
Try to set an environment variable safely.
Definition utils.c:365
char * string_replace_at(char *buf, int pos, int len, const char *repl)
Replace a substring inside a string with another string.
Definition utils.c:37
bool string_is_int(const char *str)
Check if string contains digits.
Definition utils.c:485
bool is_like_path(const char *string)
Check if the strings looks like a path.
Definition utils.c:495
FILE * fopen_verbose(const char *const path)
Open files verbosely.
Definition utils.c:458
bool safe_string_to_long_long(long long *in, const char *str)
Same as safe_string_to_int, but then for a long.
Definition utils.c:210
const char * user_get_home(void)
Retrieve the HOME directory of the user running dunst.
Definition utils.c:350
int string_array_length(char **s)
Returns the length of a string array, -1 if the input is NULL.
Definition utils.c:166
bool is_readable_file(const char *const path)
Check if file is readable.
Definition utils.c:437
gint64 time_now(void)
Get the current real time.
Definition utils.c:333
char * string_strip_brackets(const char *s)
Strips a string of it's brackets if the first and last character are a bracket.
Definition utils.c:425
bool safe_string_to_double(double *in, const char *str)
Same as safe_string_to_int, but then for a double.
Definition utils.c:245
bool safe_string_to_int(int *in, const char *str)
Convert string to int in a safe way.
Definition utils.c:229
void string_strip_delimited(char *str, char a, char b)
Strip content between two delimiter characters.
Definition utils.c:126
char * string_to_path(char *string)
Replace tilde and path-specific values with it's equivalents.
Definition utils.c:178
char * string_strip_quotes(const char *value)
Strip quotes from a string, ignoring inner quotes.
Definition utils.c:111
bool is_deprecated_section(const char *s)
This function tells if a section is deprecated.
Definition utils.c:407
char * string_replace_all(const char *needle, const char *replacement, char *haystack)
Replace all occurences of a substring.
Definition utils.c:66
bool is_special_section(const char *s)
Some sections are handled differently in dunst.
Definition utils.c:398
char * string_append(char *a, const char *b, const char *sep)
Append b to string a, then concatenate both with sep (if they are non-empty).
Definition utils.c:92
void add_paths_from_env(GPtrArray *arr, char *env_name, char *subdir, char *alternative)
Adds the contents of env_name with subdir to the array, interpreting the environment variable as a co...
Definition utils.c:472
char ** string_to_array(const char *string, const char *delimiter)
Parse a string into a dynamic array of tokens, using the delimiter string.
Definition utils.c:154
char * string_replace_char(char needle, char replacement, char *haystack)
Replaces all occurrences of the char needle with the char replacement in haystack.
Definition utils.c:27
gint64 string_to_time(const char *string)
Convert time units (ms, s, m) to the internal gint64 microseconds format.
Definition utils.c:263
String, time and other various helpers.
#define STR_EMPTY(s)
Test if a string is NULL or empty.
Definition utils.h:20
#define S2US(s)
Convert seconds into microseconds.
Definition utils.h:46
#define STRN_EQ(a, b, n)
Test if string a and b are same up to n chars.
Definition utils.h:29
#define STR_EQ(a, b)
Test if string a and b contain the same chars.
Definition utils.h:26
#define ASSERT_OR_RET(expr, val)
Assert that expr evaluates to true, if not return val.
Definition utils.h:42