1 /* properties.c -- functions to get files properties */
2 
3 /*
4  * This file is part of CliFM
5  *
6  * Copyright (C) 2016-2021, L. Abramovich <johndoe.arch@outlook.com>
7  * All rights reserved.
8 
9  * CliFM is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * CliFM is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22  * MA 02110-1301, USA.
23 */
24 
25 #include "helpers.h"
26 
27 #include <errno.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <sys/stat.h>
31 #include <time.h>
32 #include <unistd.h>
33 #ifdef __linux__
34 #include <sys/capability.h>
35 #endif
36 #include <fcntl.h>
37 #include <grp.h>
38 #include <pwd.h>
39 #include <sys/types.h>
40 
41 #include "aux.h"
42 #include "checks.h"
43 #include "colors.h"
44 
45 static int
get_properties(char * filename,const int dsize)46 get_properties(char *filename, const int dsize)
47 {
48 	if (!filename || !*filename)
49 		return EXIT_FAILURE;
50 
51 	size_t len = strlen(filename);
52 	if (filename[len - 1] == '/')
53 		filename[len - 1] = '\0';
54 
55 	/* Check file existence */
56 	struct stat attr;
57 	if (lstat(filename, &attr) == -1) {
58 		fprintf(stderr, "%s: pr: '%s': %s\n", PROGRAM_NAME, filename,
59 		    strerror(errno));
60 		return EXIT_FAILURE;
61 	}
62 
63 	/* Get file size */
64 	char *size_type = get_size_unit(attr.st_size);
65 
66 	/* Get file type (and color): */
67 	char file_type = 0;
68 	char *linkname = (char *)NULL,
69 		 *color = (char *)NULL;
70 
71 	switch (attr.st_mode & S_IFMT) {
72 	case S_IFREG: {
73 		char *ext = (char *)NULL;
74 		file_type = '-';
75 		if (light_mode)
76 			color = fi_c;
77 		else if (access(filename, R_OK) == -1)
78 			color = nf_c;
79 		else if (attr.st_mode & S_ISUID)
80 			color = su_c;
81 		else if (attr.st_mode & S_ISGID)
82 			color = sg_c;
83 		else {
84 #ifdef _LINUX_CAP
85 			cap_t cap = cap_get_file(filename);
86 			if (cap) {
87 				color = ca_c;
88 				cap_free(cap);
89 			} else if (attr.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
90 #else
91 			if (attr.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
92 #endif
93 				if (attr.st_size == 0)
94 					color = ee_c;
95 				else
96 					color = ex_c;
97 			}
98 
99 			else if (attr.st_size == 0)
100 				color = ef_c;
101 			else if (attr.st_nlink > 1)
102 				color = mh_c;
103 			else {
104 				ext = strrchr(filename, '.');
105 				if (ext) {
106 					char *extcolor = get_ext_color(ext);
107 					if (extcolor) {
108 						char ext_color[MAX_COLOR] = "";
109 						sprintf(ext_color, "\x1b[%sm", extcolor);
110 						color = ext_color;
111 						extcolor = (char *)NULL;
112 					} else  { /* No matching extension found */
113 						color = fi_c;
114 					}
115 				} else {
116 					color = fi_c;
117 				}
118 			}
119 		}
120 	} break;
121 	case S_IFDIR:
122 		file_type = 'd';
123 		if (light_mode)
124 			color = di_c;
125 		else if (access(filename, R_OK | X_OK) != 0) {
126 			color = nd_c;
127 		} else
128 			color = get_dir_color(filename, attr.st_mode);
129 
130 		break;
131 	case S_IFLNK:
132 		file_type = 'l';
133 		if (light_mode) {
134 			color = ln_c;
135 		} else {
136 			linkname = realpath(filename, (char *)NULL);
137 			if (linkname)
138 				color = ln_c;
139 			else
140 				color = or_c;
141 		}
142 		break;
143 	case S_IFSOCK: file_type = 's';
144 		color = so_c;
145 		break;
146 	case S_IFBLK:
147 		file_type = 'b';
148 		color = bd_c;
149 		break;
150 	case S_IFCHR:
151 		file_type = 'c';
152 		color = cd_c;
153 		break;
154 	case S_IFIFO:
155 		file_type = 'p';
156 		color = pi_c;
157 		break;
158 	default:
159 		file_type = '?';
160 		color = no_c;
161 	}
162 
163 	/* Get file permissions */
164 	char read_usr = '-', write_usr = '-', exec_usr = '-',
165 	     read_grp = '-', write_grp = '-', exec_grp = '-',
166 	     read_others = '-', write_others = '-', exec_others = '-';
167 
168 	mode_t val = (attr.st_mode & (mode_t)~S_IFMT);
169 	if (val & S_IRUSR) read_usr = 'r';
170 	if (val & S_IWUSR) write_usr = 'w';
171 	if (val & S_IXUSR) exec_usr = 'x';
172 
173 	if (val & S_IRGRP) read_grp = 'r';
174 	if (val & S_IWGRP) write_grp = 'w';
175 	if (val & S_IXGRP) exec_grp = 'x';
176 
177 	if (val & S_IROTH) read_others = 'r';
178 	if (val & S_IWOTH) write_others = 'w';
179 	if (val & S_IXOTH) exec_others = 'x';
180 
181 	if (attr.st_mode & S_ISUID)
182 		(val & S_IXUSR) ? (exec_usr = 's') : (exec_usr = 'S');
183 	if (attr.st_mode & S_ISGID)
184 		(val & S_IXGRP) ? (exec_grp = 's') : (exec_grp = 'S');
185 	if (attr.st_mode & S_ISVTX)
186 		(val & S_IXOTH) ? (exec_others = 't'): (exec_others = 'T');
187 
188 	/* Get number of links to the file */
189 	nlink_t link_n = attr.st_nlink;
190 
191 	/* Get modification time */
192 	time_t time = (time_t)attr.st_mtim.tv_sec;
193 	struct tm tm;
194 	localtime_r(&time, &tm);
195 	char mod_time[128] = "";
196 
197 	if (time)
198 		/* Store formatted (and localized) date-time string into
199 		 * mod_time */
200 		strftime(mod_time, sizeof(mod_time), "%b %d %H:%M:%S %Y", &tm);
201 	else
202 		mod_time[0] = '-';
203 
204 	/* Get owner and group names */
205 	uid_t owner_id = attr.st_uid; /* owner ID */
206 	gid_t group_id = attr.st_gid; /* group ID */
207 	struct group *group;
208 	struct passwd *owner;
209 	group = getgrgid(group_id);
210 	owner = getpwuid(owner_id);
211 
212 	/* Print file properties */
213 	printf("(%04o)%c/%c%c%c/%c%c%c/%c%c%c%s %zu %s %s %s %s ",
214 	    attr.st_mode & 07777, file_type,
215 	    read_usr, write_usr, exec_usr, read_grp,
216 	    write_grp, exec_grp, read_others, write_others, exec_others,
217 	    is_acl(filename) ? "+" : "", (size_t)link_n,
218 	    (!owner) ? _("unknown") : owner->pw_name,
219 	    (!group) ? _("unknown") : group->gr_name,
220 	    (size_type) ? size_type : "?",
221 	    (mod_time[0] != '\0') ? mod_time : "?");
222 
223 	if (file_type && file_type != 'l') {
224 		printf("%s%s%s\n", color, filename, df_c);
225 	} else if (linkname) {
226 		printf("%s%s%s -> %s\n", color, filename, df_c, linkname);
227 		free(linkname);
228 	} else { /* Broken link */
229 		char link[PATH_MAX] = "";
230 		ssize_t ret = readlinkat(AT_FDCWD, filename, link, PATH_MAX);
231 
232 		if (ret) {
233 			printf(_("%s%s%s -> %s (broken link)\n"), color, filename,
234 			    df_c, link);
235 		} else {
236 			printf("%s%s%s -> ???\n", color, filename, df_c);
237 		}
238 	}
239 
240 	/* Stat information */
241 	/* Last access time */
242 	time = (time_t)attr.st_atim.tv_sec;
243 	localtime_r(&time, &tm);
244 	char access_time[128] = "";
245 
246 	if (time)
247 		/* Store formatted (and localized) date-time string into
248 		 * access_time */
249 		strftime(access_time, sizeof(access_time), "%b %d %H:%M:%S %Y", &tm);
250 	else
251 		access_time[0] = '-';
252 
253 	/* Last properties change time */
254 	time = (time_t)attr.st_ctim.tv_sec;
255 	localtime_r(&time, &tm);
256 	char change_time[128] = "";
257 	if (time)
258 		strftime(change_time, sizeof(change_time), "%b %d %H:%M:%S %Y", &tm);
259 	else
260 		change_time[0] = '-';
261 
262 		/* Get creation (birth) time */
263 #if defined(HAVE_ST_BIRTHTIME) || defined(__BSD_VISIBLE__z)
264 #ifdef __OpenBSD__
265 	time = attr.__st_birthtim.tv_sec;
266 #else
267 	time = attr.st_birthtime;
268 #endif
269 	localtime_r(&time, &tm);
270 	char creation_time[128] = "";
271 	if (!time)
272 		creation_time[0] = '-';
273 	else
274 		strftime(creation_time, sizeof(creation_time),
275 		    "%b %d %H:%M:%S %Y", &tm);
276 #elif defined(_STATX)
277 	struct statx attrx;
278 	statx(AT_FDCWD, filename, AT_SYMLINK_NOFOLLOW, STATX_BTIME, &attrx);
279 	time = (time_t)attrx.stx_btime.tv_sec;
280 	localtime_r(&time, &tm);
281 	char creation_time[128] = "";
282 
283 	if (!time) {
284 		creation_time[0] = '-';
285 	} else {
286 		strftime(creation_time, sizeof(creation_time),
287 		    "%b %d %H:%M:%S %Y", &tm);
288 	}
289 #endif
290 
291 	switch (file_type) {
292 	case 'd': printf(_("Directory")); break;
293 	case 's': printf(_("Socket")); break;
294 	case 'l': printf(_("Symbolic link")); break;
295 	case 'b': printf(_("Block special file")); break;
296 	case 'c': printf(_("Character special file")); break;
297 	case 'p': printf(_("Fifo")); break;
298 	case '-': printf(_("Regular file")); break;
299 	default: break;
300 	}
301 #ifdef __OpenBSD__
302 	printf(_("\tBlocks: %lld"), attr.st_blocks);
303 #else
304 	printf(_("\tBlocks: %ld"), attr.st_blocks);
305 #endif
306 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
307 	printf(_("\tIO Block: %d"), attr.st_blksize);
308 #else
309 	printf(_("\tIO Block: %ld"), attr.st_blksize);
310 #endif
311 #ifdef __OpenBSD__
312 	printf(_("\tInode: %llu\n"), attr.st_ino);
313 #else
314 	printf(_("\tInode: %zu\n"), attr.st_ino);
315 #endif
316 #ifdef __OpenBSD__
317 	printf(_("Device: %d"), attr.st_dev);
318 #else
319 	printf(_("Device: %zu"), attr.st_dev);
320 #endif
321 	printf(_("\tUid: %u (%s)"), attr.st_uid, (!owner) ? _("unknown")
322 			: owner->pw_name);
323 	printf(_("\tGid: %u (%s)\n"), attr.st_gid, (!group) ? _("unknown")
324 			: group->gr_name);
325 
326 	/* Print file timestamps */
327 	printf(_("Access: \t%s\n"), access_time);
328 	printf(_("Modify: \t%s\n"), mod_time);
329 	printf(_("Change: \t%s\n"), change_time);
330 
331 #if defined(HAVE_ST_BIRTHTIME) || defined(__BSD_VISIBLE__z) || defined(_STATX)
332 	printf(_("Birth: \t\t%s\n"), creation_time);
333 #endif
334 
335 	/* Print size */
336 	if ((attr.st_mode & S_IFMT) == S_IFDIR) {
337 		if (dsize) {
338 			fputs(_("Total size: \t"), stdout);
339 			off_t total_size = dir_size(filename);
340 			if (total_size != -1) {
341 				char *human_size = get_size_unit(total_size * 1024);
342 				if (human_size) {
343 					printf("%s\n", human_size);
344 					free(human_size);
345 				} else {
346 					puts("?");
347 				}
348 			} else {
349 				puts("?");
350 			}
351 		}
352 	} else {
353 		printf(_("Size: \t\t%s\n"), size_type ? size_type : "?");
354 	}
355 
356 	if (size_type)
357 		free(size_type);
358 
359 	return EXIT_SUCCESS;
360 }
361 
362 int
363 print_entry_props(const struct fileinfo *props, size_t max)
364 {
365 	/* Get file size */
366 	char *size_type = get_size_unit(props->size);
367 
368 	/* Get file type indicator */
369 	char file_type = 0;
370 
371 	switch (props->mode & S_IFMT) {
372 	case S_IFREG: file_type = '-'; break;
373 	case S_IFDIR: file_type = 'd'; break;
374 	case S_IFLNK: file_type = 'l'; break;
375 	case S_IFSOCK: file_type = 's'; break;
376 	case S_IFBLK: file_type = 'b'; break;
377 	case S_IFCHR: file_type = 'c'; break;
378 	case S_IFIFO: file_type = 'p'; break;
379 	default: file_type = '?';
380 	}
381 
382 	/* Get file permissions */
383 	char read_usr = '-', write_usr = '-', exec_usr = '-',
384 	     read_grp = '-', write_grp = '-', exec_grp = '-',
385 	     read_others = '-', write_others = '-', exec_others = '-';
386 
387 	mode_t val = (props->mode & (mode_t)~S_IFMT);
388 	if (val & S_IRUSR) read_usr = 'r';
389 	if (val & S_IWUSR) write_usr = 'w';
390 	if (val & S_IXUSR) exec_usr = 'x';
391 
392 	if (val & S_IRGRP) read_grp = 'r';
393 	if (val & S_IWGRP) write_grp = 'w';
394 	if (val & S_IXGRP) exec_grp = 'x';
395 
396 	if (val & S_IROTH) read_others = 'r';
397 	if (val & S_IWOTH) write_others = 'w';
398 	if (val & S_IXOTH) exec_others = 'x';
399 
400 	if (props->mode & S_ISUID)
401 		(val & S_IXUSR) ? (exec_usr = 's') : (exec_usr = 'S');
402 	if (props->mode & S_ISGID)
403 		(val & S_IXGRP) ? (exec_grp = 's') : (exec_grp = 'S');
404 	if (props->mode & S_ISVTX)
405 		(val & S_IXOTH) ? (exec_others = 't'): (exec_others = 'T');
406 
407 	/* Get modification time */
408 	char mod_time[128];
409 	if (props->ltime) {
410 		struct tm t;
411 		localtime_r(&props->ltime, &t);
412 		snprintf(mod_time, 128, "%d-%02d-%02d %02d:%02d", t.tm_year + 1900,
413 		    t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min);
414 	} else {
415 		strcpy(mod_time, "-               ");
416 	}
417 
418 	/* Get owner and group names */
419 	/*  struct group *group;
420 	struct passwd *owner;
421 	group = getgrgid(props->uid);
422 	owner = getpwuid(props->gid); */
423 
424 	/*  If file name length is greater than max, truncate it
425 	 * to max (later a tilde (~) will be appended to let the user know
426 	 * the file name was truncated) */
427 	char tname[PATH_MAX * sizeof(wchar_t)];
428 	int trim = 0;
429 
430 	size_t cur_len = 0;
431 	if (elnpad == NOPAD)
432 		cur_len = (size_t)props->eln_n + 1 + props->len;
433 	else
434 		cur_len = (size_t)DIGINUM(files + 1) + 1 + props->len;
435 #ifndef _NO_ICONS
436 	if (icons) {
437 		cur_len += 3;
438 		max += 3;
439 	}
440 #endif
441 
442 	int diff = 0;
443 	if (cur_len > max) {
444 		int rest = (int)(cur_len - max);
445 		trim = 1;
446 		strncpy(tname, props->name, (PATH_MAX * sizeof(wchar_t)) - 1);
447 		int a = (int)props->len - rest - 1;
448 		if (a < 0)
449 			a = 0;
450 		if (unicode)
451 			diff = u8truncstr(tname, (size_t)(a));
452 		else
453 			tname[a] = '\0';
454 
455 		cur_len -= (size_t)rest;
456 	}
457 
458 	/* Calculate pad for each file */
459 	int pad;
460 	pad = (int)(max - cur_len);
461 	if (pad < 0)
462 		pad = 0;
463 
464 	if (!trim || !unicode)
465 		mbstowcs((wchar_t *)tname, props->name, PATH_MAX);
466 
467 #ifndef _NO_ICONS
468 	printf("%s%s%c%s%s%ls\x1b[%dC%s%-*s%s%s %c/%c%c%c/%c%c%c/%c%c%c%s  "
469 	       "%u:%u  %s  %s\n",
470 	    colorize ? props->icon_color : "",
471 	    icons ? props->icon : "", icons ? ' ' : 0, df_c,
472 #else
473 	printf("%s%ls\x1b[%dC%s%-*s%s%s %c/%c%c%c/%c%c%c/%c%c%c%s  "
474 	       "%u:%u  %s  %s\n",
475 #endif
476 	    colorize ? props->color : "",
477 		(wchar_t *)tname, diff > 0 ? diff : -1,
478 	    light_mode ? "" : df_c, pad, "", df_c,
479 	    trim ? "\x1b[1;31m~\x1b[0m" : "", file_type,
480 	    read_usr, write_usr, exec_usr,
481 	    read_grp, write_grp, exec_grp,
482 	    read_others, write_others, exec_others,
483 	    is_acl(props->name) ? "+" : "",
484 	    /*          !owner ? _("?") : owner->pw_name,
485 			!group ? _("?") : group->gr_name, */
486 	    props->uid, props->gid,
487 	    *mod_time ? mod_time : "?",
488 	    size_type ? size_type : "?");
489 
490 	if (size_type)
491 		free(size_type);
492 
493 	return EXIT_SUCCESS;
494 }
495 
496 int
497 properties_function(char **comm)
498 {
499 	if (!comm)
500 		return EXIT_FAILURE;
501 
502 	size_t i;
503 	int exit_status = EXIT_SUCCESS;
504 	int _dir_size = 0;
505 
506 	if (*comm[0] == 'p' && comm[0][1] == 'p' && !comm[0][2])
507 		_dir_size = 1;
508 
509 	/* If "pr file file..." */
510 	for (i = 1; i <= args_n; i++) {
511 		if (strchr(comm[i], '\\')) {
512 			char *deq_file = dequote_str(comm[i], 0);
513 			if (!deq_file) {
514 				fprintf(stderr, _("%s: %s: Error dequoting file name\n"),
515 				    PROGRAM_NAME, comm[i]);
516 				exit_status = EXIT_FAILURE;
517 				continue;
518 			}
519 
520 			strcpy(comm[i], deq_file);
521 			free(deq_file);
522 		}
523 
524 		if (get_properties(comm[i], _dir_size) != 0)
525 			exit_status = EXIT_FAILURE;
526 	}
527 
528 	return exit_status;
529 }
530