1 /* vifm
2  * Copyright (C) 2001 Ken Steen.
3  * Copyright (C) 2011 xaizek.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include "utils.h"
21 #include "utils_int.h"
22 
23 #ifdef _WIN32
24 #include <windows.h>
25 #include <winioctl.h>
26 
27 #include "utf8.h"
28 #endif
29 
30 #include <sys/types.h> /* pid_t */
31 #include <unistd.h>
32 
33 #include <ctype.h> /* isalnum() isalpha() iscntrl() */
34 #include <errno.h> /* errno */
35 #include <math.h> /* modf() pow() */
36 #include <stddef.h> /* size_t */
37 #include <stdio.h> /* snprintf() */
38 #include <stdlib.h> /* free() malloc() qsort() */
39 #include <string.h> /* memcpy() strdup() strchr() strlen() strpbrk() strtol() */
40 #include <wchar.h> /* wcwidth() */
41 
42 #include "../cfg/config.h"
43 #include "../compat/fs_limits.h"
44 #include "../compat/os.h"
45 #include "../engine/keys.h"
46 #include "../engine/variables.h"
47 #include "../int/fuse.h"
48 #include "../modes/dialogs/msg_dialog.h"
49 #include "../ui/cancellation.h"
50 #include "../ui/ui.h"
51 #include "../background.h"
52 #include "../registers.h"
53 #include "../status.h"
54 #include "env.h"
55 #include "file_streams.h"
56 #include "fs.h"
57 #include "log.h"
58 #include "macros.h"
59 #include "path.h"
60 #include "str.h"
61 #include "string_array.h"
62 
63 static void show_progress_cb(const void *descr);
64 static const char ** get_size_suffixes(void);
65 static double split_size_double(double d, unsigned long long *ifraction,
66 		int *fraction_width);
67 #ifdef _WIN32
68 static void unquote(char quoted[]);
69 #endif
70 static int is_line_spec(const char str[]);
71 
72 /* Size suffixes of different kinds. */
73 static const char *iec_i_units[] = {
74 	"  B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"
75 };
76 static const char *iec_units[] = {
77 	"B", "K", "M", "G", "T", "P", "E", "Z", "Y"
78 };
79 static const char *si_units[] = {
80 	" B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"
81 };
82 ARRAY_GUARD(iec_units, ARRAY_LEN(iec_i_units));
83 ARRAY_GUARD(si_units, ARRAY_LEN(iec_units));
84 
85 int
vifm_system(char command[],ShellRequester by)86 vifm_system(char command[], ShellRequester by)
87 {
88 #ifdef _WIN32
89 	/* The check is primarily for tests, otherwise screen is reset. */
90 	if(curr_stats.load_stage != 0)
91 	{
92 		system("cls");
93 	}
94 #endif
95 	LOG_INFO_MSG("Shell command: %s", command);
96 	return run_in_shell_no_cls(command, by);
97 }
98 
99 int
process_cmd_output(const char descr[],const char cmd[],int user_sh,int interactive,cmd_output_handler handler,void * arg)100 process_cmd_output(const char descr[], const char cmd[], int user_sh,
101 		int interactive, cmd_output_handler handler, void *arg)
102 {
103 	FILE *file, *err;
104 	pid_t pid;
105 	int nlines;
106 	char **lines;
107 	int i;
108 
109 	LOG_INFO_MSG("Capturing output of the command: %s", cmd);
110 
111 	pid = bg_run_and_capture((char *)cmd, user_sh, &file, &err);
112 	if(pid == (pid_t)-1)
113 	{
114 		return 1;
115 	}
116 
117 	ui_cancellation_push_on();
118 
119 	if(!interactive)
120 	{
121 		show_progress("", 0);
122 	}
123 
124 	wait_for_data_from(pid, file, 0, &ui_cancellation_info);
125 	lines = read_stream_lines(file, &nlines, 1,
126 			interactive ? NULL : &show_progress_cb, descr);
127 
128 	ui_cancellation_pop();
129 	fclose(file);
130 
131 	for(i = 0; i < nlines; ++i)
132 	{
133 		handler(lines[i], arg);
134 	}
135 	free_string_array(lines, nlines);
136 
137 	show_errors_from_file(err, descr);
138 	return 0;
139 }
140 
141 /* Callback implementation for read_stream_lines(). */
142 static void
show_progress_cb(const void * descr)143 show_progress_cb(const void *descr)
144 {
145 	show_progress(descr, -250);
146 }
147 
148 int
vifm_chdir(const char path[])149 vifm_chdir(const char path[])
150 {
151 	char curr_path[PATH_MAX + 1];
152 	if(get_cwd(curr_path, sizeof(curr_path)) == curr_path)
153 	{
154 		if(stroscmp(curr_path, path) == 0)
155 		{
156 			return 0;
157 		}
158 	}
159 	return os_chdir(path);
160 }
161 
162 char *
expand_path(const char path[])163 expand_path(const char path[])
164 {
165 	char *const expanded_envvars = expand_envvars(path, 0);
166 	/* replace_tilde() frees memory pointed to by expand_envvars. */
167 	return replace_tilde(expanded_envvars);
168 }
169 
170 char *
expand_envvars(const char str[],int escape_vals)171 expand_envvars(const char str[], int escape_vals)
172 {
173 	char *result = NULL;
174 	size_t len = 0;
175 	int prev_slash = 0;
176 	while(*str != '\0')
177 	{
178 		if(!prev_slash && *str == '$' && isalpha(str[1]))
179 		{
180 			char var_name[NAME_MAX + 1];
181 			const char *p = str + 1;
182 			char *q = var_name;
183 			const char *var_value;
184 
185 			while((isalnum(*p) || *p == '_') &&
186 					(size_t)(q - var_name) < sizeof(var_name) - 1)
187 			{
188 				*q++ = *p++;
189 			}
190 			*q = '\0';
191 
192 			var_value = local_getenv(var_name);
193 			if(!is_null_or_empty(var_value))
194 			{
195 				char *escaped_var_value = NULL;
196 				if(escape_vals)
197 				{
198 					escaped_var_value = shell_like_escape(var_value, 2);
199 					var_value = escaped_var_value;
200 				}
201 
202 				result = extend_string(result, var_value, &len);
203 				free(escaped_var_value);
204 
205 				str = p;
206 			}
207 			else
208 			{
209 				str++;
210 			}
211 		}
212 		else
213 		{
214 			prev_slash = (*str == '\\') ? !prev_slash : 0;
215 
216 			if(!prev_slash || escape_vals)
217 			{
218 				const char single_char[] = { *str, '\0' };
219 				result = extend_string(result, single_char, &len);
220 			}
221 
222 			str++;
223 		}
224 	}
225 	if(result == NULL)
226 		result = strdup("");
227 	return result;
228 }
229 
230 int
friendly_size_notation(uint64_t num,int str_size,char str[])231 friendly_size_notation(uint64_t num, int str_size, char str[])
232 {
233 	unsigned long long fraction = 0ULL;
234 	int fraction_width;
235 	const char **const units = get_size_suffixes();
236 	size_t u = 0U;
237 	double d = num;
238 
239 	while(d >= cfg.sizefmt.base - 0.5 && u < ARRAY_LEN(iec_i_units) - 1U)
240 	{
241 		d /= cfg.sizefmt.base;
242 		++u;
243 	}
244 
245 	/* Fractional part is ignored when it's absent (we didn't divide anything) and
246 	 * when we format size in previously used manner and hide it for values
247 	 * greater than 9. */
248 	if(u != 0U && !(cfg.sizefmt.precision == 0 && d > 9.0))
249 	{
250 		d = split_size_double(d, &fraction, &fraction_width);
251 	}
252 
253 	if(fraction == 0)
254 	{
255 		snprintf(str, str_size, "%.0f%s%s", d, cfg.sizefmt.space ? " " : "",
256 				units[u]);
257 	}
258 	else
259 	{
260 		snprintf(str, str_size, "%.0f.%0*" PRINTF_ULL "%s%s", d, fraction_width,
261 				fraction, cfg.sizefmt.space ? " " : "", units[u]);
262 	}
263 
264 	return u > 0U;
265 }
266 
267 /* Picks size suffixes as per configuration.  Returns one of *_units arrays. */
268 static const char **
get_size_suffixes(void)269 get_size_suffixes(void)
270 {
271 	return (cfg.sizefmt.base == 1000) ? si_units :
272 	       (cfg.sizefmt.ieci_prefixes ? iec_i_units : iec_units);
273 }
274 
275 /* Breaks size (floating point, not integer) into integer and fractional parts
276  * rounding and truncating them as necessary.  Sets *ifraction and
277  * *fraction_width.  Returns new value for the size (truncated and/or
278  * rounded). */
279 static double
split_size_double(double size,unsigned long long * ifraction,int * fraction_width)280 split_size_double(double size, unsigned long long *ifraction,
281 		int *fraction_width)
282 {
283 	double ten_power, dfraction, integer;
284 
285 	*fraction_width = (cfg.sizefmt.precision == 0) ? 1 : cfg.sizefmt.precision;
286 
287 	ten_power = pow(10, *fraction_width + 1);
288 	while(ten_power > ULLONG_MAX)
289 	{
290 		ten_power /= 10;
291 		--*fraction_width;
292 	}
293 
294 	dfraction = modf(size, &integer);
295 	*ifraction = DIV_ROUND_UP(ten_power*dfraction, 10);
296 
297 	if(*ifraction >= ten_power/10.0)
298 	{
299 		/* Overflow into integer part during rounding. */
300 		integer += 1.0;
301 		*ifraction -= ten_power/10.0;
302 	}
303 	else if(*ifraction == 0 && dfraction != 0.0)
304 	{
305 		/* Fractional part is too small, "round up" to make it visible. */
306 		*ifraction = 1;
307 	}
308 
309 	/* Skip trailing zeroes. */
310 	for(; *fraction_width != 0 && *ifraction%10 == 0; --*fraction_width)
311 	{
312 		*ifraction /= 10;
313 	}
314 
315 	return (*ifraction == 0) ? size : integer;
316 }
317 
318 const char *
enclose_in_dquotes(const char str[])319 enclose_in_dquotes(const char str[])
320 {
321 	static char buf[1 + PATH_MAX*2 + 1 + 1];
322 	char *p;
323 
324 	p = buf;
325 	*p++ = '"';
326 	while(*str != '\0')
327 	{
328 		if((curr_stats.shell_type != ST_PS && *str == '\\') || *str == '"' ||
329 				(curr_stats.shell_type == ST_NORMAL && (*str == '$' || *str == '`')))
330 		{
331 			*p++ = '\\';
332 		}
333 		*p++ = *str;
334 
335 		str++;
336 	}
337 	*p++ = '"';
338 	*p = '\0';
339 	return buf;
340 }
341 
342 const char *
make_name_unique(const char filename[])343 make_name_unique(const char filename[])
344 {
345 	static char unique[PATH_MAX + 1];
346 	size_t len;
347 	int i;
348 
349 #ifndef _WIN32
350 	snprintf(unique, sizeof(unique), "%s_%u%u_00", filename, getppid(),
351 			get_pid());
352 #else
353 	/* TODO: think about better name uniqualization on Windows. */
354 	snprintf(unique, sizeof(unique), "%s_%u_00", filename, get_pid());
355 #endif
356 	len = strlen(unique);
357 	i = 0;
358 
359 	while(path_exists(unique, DEREF))
360 	{
361 		sprintf(unique + len - 2, "%d", ++i);
362 	}
363 	return unique;
364 }
365 
366 char *
extract_cmd_name(const char line[],int raw,size_t buf_len,char buf[])367 extract_cmd_name(const char line[], int raw, size_t buf_len, char buf[])
368 {
369 	const char *result;
370 #ifdef _WIN32
371 	int left_quote, right_quote = 0;
372 #endif
373 
374 	line = skip_whitespace(line);
375 
376 #ifdef _WIN32
377 	if((left_quote = (line[0] == '"')))
378 	{
379 		result = strchr(line + 1, '"');
380 	}
381 	else
382 #endif
383 	{
384 		result = strchr(line, ' ');
385 	}
386 	if(result == NULL)
387 	{
388 		result = line + strlen(line);
389 	}
390 
391 #ifdef _WIN32
392 	if(left_quote && (right_quote = (result[0] == '"')))
393 	{
394 		result++;
395 	}
396 #endif
397 	copy_str(buf, MIN((size_t)(result - line + 1), buf_len), line);
398 #ifdef _WIN32
399 	if(!raw && left_quote && right_quote)
400 	{
401 		unquote(buf);
402 	}
403 #endif
404 	if(!raw)
405 	{
406 		fuse_strip_mount_metadata(buf);
407 	}
408 	result = skip_whitespace(result);
409 
410 	return (char *)result;
411 }
412 
413 #ifdef _WIN32
414 /* Removes first and the last charater of the string, if they are quotes. */
415 static void
unquote(char quoted[])416 unquote(char quoted[])
417 {
418 	size_t len = strlen(quoted);
419 	if(len > 2 && quoted[0] == quoted[len - 1] && strpbrk(quoted, "\"'`") != NULL)
420 	{
421 		memmove(quoted, quoted + 1, len - 2);
422 		quoted[len - 2] = '\0';
423 	}
424 }
425 #endif
426 
427 int
vifm_wcwidth(wchar_t wc)428 vifm_wcwidth(wchar_t wc)
429 {
430 	const int width = wcwidth(wc);
431 	if(width == -1)
432 	{
433 		return ((size_t)wc < (size_t)L' ') ? 2 : 1;
434 	}
435 	return width;
436 }
437 
438 int
vifm_wcswidth(const wchar_t str[],size_t n)439 vifm_wcswidth(const wchar_t str[], size_t n)
440 {
441 	int width = 0;
442 	while(*str != L'\0' && n--)
443 	{
444 		width += vifm_wcwidth(*str++);
445 	}
446 	return width;
447 }
448 
449 char *
escape_for_squotes(const char string[],size_t offset)450 escape_for_squotes(const char string[], size_t offset)
451 {
452 	/* TODO: maybe combine code with escape_for_dquotes(). */
453 
454 	size_t len;
455 	char *escaped, *out;
456 
457 	len = strlen(string);
458 
459 	escaped = malloc(len*2 + 1);
460 	out = escaped;
461 
462 	/* Copy prefix not escaping it. */
463 	offset = MIN(len, offset);
464 	memcpy(out, string, offset);
465 	out += offset;
466 	string += offset;
467 
468 	while(*string != '\0')
469 	{
470 		if(*string == '\'')
471 		{
472 			*out++ = '\'';
473 		}
474 
475 		*out++ = *string++;
476 	}
477 	*out = '\0';
478 	return escaped;
479 }
480 
481 char *
escape_for_dquotes(const char string[],size_t offset)482 escape_for_dquotes(const char string[], size_t offset)
483 {
484 	/* TODO: maybe combine code with escape_for_squotes(). */
485 
486 	size_t len;
487 	char *escaped, *out;
488 
489 	len = strlen(string);
490 
491 	escaped = malloc(len*2 + 1);
492 	out = escaped;
493 
494 	/* Copy prefix not escaping it. */
495 	offset = MIN(len, offset);
496 	memcpy(out, string, offset);
497 	out += offset;
498 	string += offset;
499 
500 	while(*string != '\0')
501 	{
502 		switch(*string)
503 		{
504 			case '\a': *out++ = '\\'; *out++ = 'a'; break;
505 			case '\b': *out++ = '\\'; *out++ = 'b'; break;
506 			case '\f': *out++ = '\\'; *out++ = 'f'; break;
507 			case '\n': *out++ = '\\'; *out++ = 'n'; break;
508 			case '\r': *out++ = '\\'; *out++ = 'r'; break;
509 			case '\t': *out++ = '\\'; *out++ = 't'; break;
510 			case '\v': *out++ = '\\'; *out++ = 'v'; break;
511 			case '"':  *out++ = '\\'; *out++ = '"'; break;
512 
513 			default:
514 				*out++ = *string;
515 				break;
516 		}
517 		++string;
518 	}
519 	*out = '\0';
520 	return escaped;
521 }
522 
523 char *
escape_unreadable(const char str[])524 escape_unreadable(const char str[])
525 {
526 	char *escaped = malloc(strlen(str)*2 + 1);
527 
528 	char *out = escaped;
529 	while(*str != '\0')
530 	{
531 		if(iscntrl((unsigned char)*str))
532 		{
533 			*out++ = '^';
534 			*out++ = *str ^ 64;
535 		}
536 		else
537 		{
538 			*out++ = *str;
539 		}
540 		++str;
541 	}
542 	*out = '\0';
543 
544 	return escaped;
545 }
546 
547 void
expand_percent_escaping(char s[])548 expand_percent_escaping(char s[])
549 {
550 	char *p;
551 
552 	p = s;
553 	while(s[0] != '\0')
554 	{
555 		if(s[0] == '%' && s[1] == '%')
556 		{
557 			++s;
558 		}
559 		*p++ = *s++;
560 	}
561 	*p = '\0';
562 }
563 
564 void
expand_squotes_escaping(char s[])565 expand_squotes_escaping(char s[])
566 {
567 	char *p;
568 	int sq_found;
569 
570 	p = s++;
571 	sq_found = *p == '\'';
572 	while(*p != '\0')
573 	{
574 		if(*s == '\'' && sq_found)
575 		{
576 			sq_found = 0;
577 		}
578 		else
579 		{
580 			*++p = *s;
581 			sq_found = *s == '\'';
582 		}
583 		s++;
584 	}
585 }
586 
587 void
expand_dquotes_escaping(char s[])588 expand_dquotes_escaping(char s[])
589 {
590 	static const char table[] =
591 						/* 00  01  02  03  04  05  06  07  08  09  0a  0b  0c  0d  0e  0f */
592 	/* 00 */	"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
593 	/* 10 */	"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
594 	/* 20 */	"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
595 	/* 30 */	"\x00\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
596 	/* 40 */	"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
597 	/* 50 */	"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
598 	/* 60 */	"\x60\x07\x0b\x63\x64\x65\x0c\x67\x68\x69\x6a\x6b\x6c\x6d\x0a\x6f"
599 	/* 70 */	"\x70\x71\x0d\x73\x09\x75\x0b\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
600 	/* 80 */	"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
601 	/* 90 */	"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
602 	/* a0 */	"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
603 	/* b0 */	"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
604 	/* c0 */	"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
605 	/* d0 */	"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
606 	/* e0 */	"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
607 	/* f0 */	"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
608 
609 	char *str = s;
610 	char *p;
611 
612 	p = s;
613 	while(*s != '\0')
614 	{
615 		if(*s != '\\')
616 		{
617 			*p++ = *s++;
618 			continue;
619 		}
620 		s++;
621 		if(*s == '\0')
622 		{
623 			LOG_ERROR_MSG("Escaped eol in \"%s\"", str);
624 			break;
625 		}
626 		*p++ = table[(int)*s++];
627 	}
628 	*p = '\0';
629 }
630 
631 int
def_reg(int reg)632 def_reg(int reg)
633 {
634 	return (reg == NO_REG_GIVEN) ? DEFAULT_REG_NAME : reg;
635 }
636 
637 int
def_count(int count)638 def_count(int count)
639 {
640 	return (count == NO_COUNT_GIVEN) ? 1 : count;
641 }
642 
643 char *
parse_line_for_path(const char line[],const char cwd[])644 parse_line_for_path(const char line[], const char cwd[])
645 {
646 	int line_num;
647 	/* Skip empty lines. */
648 	return (skip_whitespace(line)[0] == '\0')
649 	     ? NULL
650 	     : parse_file_spec(line, &line_num, cwd);
651 }
652 
653 char *
parse_file_spec(const char spec[],int * line_num,const char cwd[])654 parse_file_spec(const char spec[], int *line_num, const char cwd[])
655 {
656 	char *path_buf;
657 	const char *colon;
658 	const size_t bufs_len = strlen(cwd) + 1U + strlen(spec) + 1U + 1U;
659 	char canonicalized[PATH_MAX + 1];
660 
661 	path_buf = malloc(bufs_len);
662 	if(path_buf == NULL)
663 	{
664 		return NULL;
665 	}
666 
667 	if(is_path_absolute(spec) || spec[0] == '~')
668 	{
669 		path_buf[0] = '\0';
670 	}
671 	else
672 	{
673 		snprintf(path_buf, bufs_len, "%s/", cwd);
674 	}
675 
676 #ifdef _WIN32
677 	colon = strchr(spec + (is_path_absolute(spec) ? 2 : 0), ':');
678 	if(colon != NULL && !is_line_spec(colon + 1))
679 	{
680 		colon = NULL;
681 	}
682 #else
683 	colon = strchr(spec, ':');
684 	while(colon != NULL)
685 	{
686 		if(is_line_spec(colon + 1))
687 		{
688 			char path[bufs_len];
689 			strcpy(path, path_buf);
690 			strncat(path, spec, colon - spec);
691 			if(path_exists(path, NODEREF))
692 			{
693 				break;
694 			}
695 		}
696 
697 		colon = strchr(colon + 1, ':');
698 	}
699 #endif
700 
701 	if(colon != NULL)
702 	{
703 		strncat(path_buf, spec, colon - spec);
704 		*line_num = atoi(colon + 1);
705 	}
706 	else
707 	{
708 		strcat(path_buf, spec);
709 		*line_num = 1;
710 
711 		while(!path_exists(path_buf, NODEREF) && strchr(path_buf, ':') != NULL)
712 		{
713 			break_atr(path_buf, ':');
714 		}
715 	}
716 
717 	chomp(path_buf);
718 	canonicalize_path(path_buf, canonicalized, sizeof(canonicalized));
719 
720 	system_to_internal_slashes(canonicalized);
721 
722 	if(!ends_with_slash(path_buf) && !is_root_dir(canonicalized) &&
723 			strcmp(canonicalized, "./") != 0)
724 	{
725 		chosp(canonicalized);
726 	}
727 
728 	free(path_buf);
729 
730 	return replace_tilde(strdup(canonicalized));
731 }
732 
733 /* Checks whether str points to a valid line number.  Returns non-zero if so,
734  * otherwise zero is returned. */
735 static int
is_line_spec(const char str[])736 is_line_spec(const char str[])
737 {
738 	char *endptr;
739 	errno = 0;
740 	(void)strtol(str, &endptr, 10);
741 	return (endptr != str && errno == 0 && *endptr == ':');
742 }
743 
744 void
safe_qsort(void * base,size_t nmemb,size_t size,int (* compar)(const void *,const void *))745 safe_qsort(void *base, size_t nmemb, size_t size,
746 		int (*compar)(const void *, const void *))
747 {
748 	if(nmemb != 0U)
749 	{
750 		/* Even when there are no entries, qsort() shouldn't be called with a NULL
751 		 * parameter.  It isn't allowed by the standard. */
752 		qsort(base, nmemb, size, compar);
753 	}
754 }
755 
756 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
757 /* vim: set cinoptions+=t0 filetype=c : */
758