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