/
findfiles.c
1321 lines (1161 loc) · 55.3 KB
/
findfiles.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*******************************************************************************
********************************************************************************
findfiles: find files based on various selection criteria
Copyright (C) 2016-2024 James S. Crook
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
********************************************************************************
*******************************************************************************/
/*******************************************************************************
findfiles searches Linux/UNIX file systems for objects (files, directories and
"other") and lists them in sorted order of last modification and/or last access.
The behavior is controlled by command line arguments as follows:
1. arguments are processed left-to-right
2. only specified target(s) are searched
3. (optional) selection by last modification age(s) or timestamp(s)
4. (optional) selection by last access age(s) or timestamp(s)
5. (optional) selection by object name pattern matching using ERE(s)
See the usage/help message for additional options.
There are 3 main "times": "starttime", "targettime" and "objecttime"
Like the OS, findfiles stores each of these in two separate variables:
"starttime" is actually starttime_s and starttime_ns
"targettime" is actually targettime_s and targettime_ns
"objecttime" is actually objecttime_s and objecttime_ns
These are the number of seconds (s) & nanoseconds (ns) since "the epoch"
(1970-01-01 00:00:00.000000000).
findfiles sets "starttime" to the current system time when it starts.
targettime is calculated as either:
1. Relative (to start time): Both of the optional age of last modification ('-m')
and age of last access ('-a') calculate a "targettime" relative to "startime".
Note that "targettime" is never later (larger than) "starttime".
2. Absolute: e.g. YYYYMMDD_HHMMSS[.fraction_of_a_second]
Here is a timeline with time increasing to the right:
"targettime" "starttime"
v v
-------------olderthanttargettimeInewerthanttargettime---------------> -m & -a
------------olderthanttargettime) (newerthanttargettime--------------> -M & -A
For example:
"-fm -10m" : find files modified <= 10 mins ago (modified after "targettime")
"-fm 10m" : find files modified >= 10 mins ago (modified before "targettime")
Note that in both cases, "targettime" is 10 minutes _before_ "starttime"! So,
the numerical value and unit ("10m", in both cases above) sets "targettime" to
10 minutes before "starttime", and "-" causes findfiles to list objects last
modified/accessed more recently ("newer") than "targettime".
The optional last modification reference object ("-M") and last access reference
object ("-A") use the minus sign ("-") in the same way as "-m" and "-a". I.e.,
"-fA -ref_file" : find files accessed after ref_file was (after "targettime")
"-fA ref_file" : find files accessed before ref_file was (before "targettime")
Note that "-m" and "-a" use <= and/or >=, but "-M" and "-A" use < and/or >!
It is assumed that, in general, the cases of file system objects having future
last access and/or last modification times are both rare and uninteresting.
*******************************************************************************/
#define PROGRAMVERSIONSTRING "3.3.0"
#define _GNU_SOURCE /* required for strptime */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <locale.h>
#include <dirent.h>
#include <regex.h>
#include <ctype.h>
#define MAX(x,y) ((x)>(y)?(x):(y))
#define SECONDSPERMINUTE 60
#define MINUTESPERHOUR 60
#define HOURSPERDAY 24
#define MAXOBJAGESTRLEN 32
#define TMBASEMONTH 1
#define TMBASEYEAR 1900
#define SECONDSPERHOUR (SECONDSPERMINUTE*MINUTESPERHOUR)
#define SECONDSPERDAY (SECONDSPERMINUTE*MINUTESPERHOUR*HOURSPERDAY)
#define SECONDSPERWEEK (SECONDSPERMINUTE*MINUTESPERHOUR*HOURSPERDAY*7)
#define MINUTESPERDAY (MINUTESPERHOUR*HOURSPERDAY)
#define NANOSECONDSPERSECOND 1000000000
#define NANOSECONDSSTR "1000000000"
#define MAXRECURSIONDEPTH 256
#define MAXDATESTRLENGTH 64
#define MAXPATHLENGTH 2048
#define INITMAXNUMOBJS ( 8*1024) /* Allocate the object table to hold up to this many entries. */
#define MAXNUMOBJSMLTFCT 2 /* Dynamically increase the object table size by this factor... */
#define MAXNUMOBJSMLTLIM (512*1024) /* up to this number. After that, ... */
#define MAXNUMOBJSINCVAL ( 64*1024) /* increment the size by this value. */
#define PATHDELIMITERCHAR '/'
#define MODTIMEINFOCHAR 'm'
#define REFMODTIMECHAR 'M'
#define SECONDSUNITCHAR 's'
#define BYTESUNITCHAR 'B'
#define DECIMALSEPARATORCHAR '.'
#define POSITIVESIGNCHAR '+'
#define NEGATIVESIGNCHAR '-'
#define DEFAULTAGE 0
#define REG_MATCH 0 /* the counterpart to (defined) REG_NOMATCH */
#define NOWSTR "Now"
#define SECONDSFORMATSTR "%S"
#define FF_STARTTIMESTR "FF_STARTTIME"
#define DEFAULTTIMESTAMPFMT "%Y%m%d_%H%M%S"
#define GETOPTSTR "+dforip:P:x:X:t:D:V:a:m:A:M:hHnsuLRTv"
typedef struct {
char *name;
char *defaultvalue;
char **valueptr;
} Envvar;
char *starttimestr;
char *datetimeformatstr;
char *ageformatstr;
char *infodatetimeformatstr;
char *timestampformatstr;
Envvar envvartable[] = {
{ "FF_AGEFORMAT", "%7ldD_%02ld:%02ld:%02ld", &ageformatstr },
{ "FF_DATETIMEFORMAT", "%04d%02d%02d_%02d%02d%02d", &datetimeformatstr },
{ "FF_INFODATETIMEFORMAT", "%a %b %d %H:%M:%S %Y %Z %z", &infodatetimeformatstr },
{ FF_STARTTIMESTR, NOWSTR, &starttimestr },
{ "FF_TIMESTAMPFORMAT", DEFAULTTIMESTAMPFMT, ×tampformatstr },
};
/* Units to display after suitably scaled numbers for "human readable" file size output
These should all be the same length and have no whitespace (sort of "right justified"). */
typedef struct {
char unitstring[4];
size_t sizelimit;
} Unitinfo;
Unitinfo humanunit1024table[] = {
{ "__B", 1024L },
{ "kiB", 1024L*1024L },
{ "MiB", 1024L*1024L*1024L },
{ "GiB", 1024L*1024L*1024L*1024L },
{ "TiB", 1024L*1024L*1024L*1024L*1024L },
{ "PiB", 1024L*1024L*1024L*1024L*1024L*1024L },
};
/* Recommendation: keep the same number of Unitinfo entries in both the 1024 and the 1000 tables */
Unitinfo humanunit1000table[] = {
{ "_B", 1000L },
{ "kB", 1000L*1000L },
{ "MB", 1000L*1000L*1000L },
{ "GB", 1000L*1000L*1000L*1000L },
{ "TB", 1000L*1000L*1000L*1000L*1000L },
{ "PB", 1000L*1000L*1000L*1000L*1000L*1000L },
};
Unitinfo *humanunittable = NULL;
size_t numhumanunits;
typedef struct { /* each object's name, modification XOR access time & size */
char *name;
time_t time_s;
time_t time_ns;
off_t size;
mode_t type;
} Objectinfo;
Objectinfo *objectinfotable;
time_t starttime_s;
time_t starttime_ns;
time_t targettime_s = DEFAULTAGE; /* set default, 0 s, and */
time_t targettime_ns = DEFAULTAGE; /* 0 ns - find files of all ages */
/* ERE: Extended Regular Expression */
#define MAXNUMERES 4
typedef struct { /* preg: pre-compiled (extended) regular expression pattern buffer */
regex_t compiledere; /* compiled ERE (preg) */
int matchcode; /* REG_MATCH or REG_NOMATCH */
} Ereinfo;
Ereinfo eretable[MAXNUMERES];
int numeres = 0;
int maxnumberobjects = INITMAXNUMOBJS;
int numobjsfound = 0;
int numtargets = 0;
int returncode = 0;
/* Command line option flags - all set to false */
int maxrecursiondepth = MAXRECURSIONDEPTH;
int recursiveflag = 0;
int ignorecaseflag = 0;
int regularfileflag = 0;
int directoryflag = 0;
int otherobjectflag = 0;
int verbosity = 0;
int displaysecondsflag = 0;
int displaynsecflag = 0;
int accesstimeflag = 0;
int newerthantargetflag = 0;
int followsymlinksflag = 0;
int displaytypesflag = 0;
int sortmultiplier = 1;
char secondsunitchar = ' ';
char bytesunitchar = ' ';
/* function prototypes */
void process_directory(char *, int);
/*******************************************************************************
Display the usage (help) message.
*******************************************************************************/
void display_usage_message(const char *progname) {
printf("usage (version %s):\n", PROGRAMVERSIONSTRING);
printf("%s [OPTION]... [target|-t target]... [OPTION]... [target|-t target]...\n", progname);
printf(" Some OPTIONs require arguments - these are:\n");
printf(" age : a relative age value followed by a time unit (eg, '3D')\n");
printf(" ERE : a POSIX-style Extended Regular Expression (pattern)\n");
printf(" path : the pathname of a reference object (file, directory, etc.)\n");
printf(" target : the pathname of an object (file, directory, etc.) to search\n");
printf(" time : an absolute date/time stamp value (eg, '20210630_121530.5')\n");
printf(" OPTIONs - can be toggled on/off (parsed left to right):\n");
printf(" -d|--directories : directories (default off)\n");
printf(" -f|--files : regular files (default off)\n");
printf(" -o|--others : other files (default off)\n");
printf(" -r|--recursive : recursive - traverse file trees (default off)\n");
printf(" -i|--ignore-case : case insensitive pattern match - use before -p|-P|-x|-X (default off)\n");
printf(" -L|--symlinks : follow symbolic Links (default off)\n");
printf(" OPTIONs requiring an argument (parsed left to right):\n");
printf(" -p|--pattern ERE : (re)initialize name search to include objects matching this ERE\n");
printf(" -P|--and-pattern ERE : extend name search to include objects also matching this ERE (logical and)\n");
printf(" -x|--exclude ERE : (re)initialize name search to exclude objects matching this ERE\n");
printf(" -X|--and-exclude ERE : extend name search to exclude objects also matching this ERE (logical and)\n");
printf(" -t|--target target_path : target path (no default)\n");
printf(" -D|--depth maximum_recursion_depth : maximum recursion traversal depth/level (default %d)\n", MAXRECURSIONDEPTH);
printf(" -V|--variable=value : for <FF_variable>=<value>\n");
printf(" Ages are relative to start time; '-3D' & '3D' both set target time to 3 days before start time\n");
printf(" -a|--acc-info [-|+]access_age : - for newer/=, [+] for older/= access ages (no default)\n");
printf(" -m|--mod-info [-|+]modification_age : - for newer/=, [+] for older/= mod ages (default 0s: any time)\n");
printf(" Times are absolute; eg, '-20211231_153000' & '20211231_153000' (using locale's timezone)\n");
printf(" -a|--acc-info [-|+]access_time : - for older/=, [+] for newer/= access times (no default)\n");
printf(" -m|--mod-info [-|+]modification_time : - for older/=, [+] for newer/= mod times (no default)\n");
printf(" Reference times are absolute; eg: '-/tmp/f' & '/tmp/f'\n");
printf(" -A|--acc-ref [-|+]acc_ref_path : - for older, [+] for newer access times (no default)\n");
printf(" -M|--mod-ref [-|+]mod_ref_path : - for older, [+] for newer mod times (no default)\n");
printf(" Flags - are 'global' options (and can NOT be toggled by setting multiple times):\n");
printf(" -h|--human-1024 : display object sizes in 'human readable' form (eg, '1.00kiB')\n");
printf(" -H|--human-1000 : display object sizes in 'human readable' form (eg, '1.02kB')\n");
printf(" -n|--nanoseconds : in verbose mode, display the maximum resolution of the OS/FS - up to ns\n");
printf(" -s|--seconds : display file ages in seconds (default D_hh:mm:ss)\n");
printf(" -u|--units : display units: s for seconds, B for Bytes (default off)\n");
printf(" -R|--reverse : Reverse the (time) order of the output (default off)\n");
printf(" -T|--types : Display the type of each file/directory/other (default off)\n");
printf(" Verbosity: (May be specified more than once for additional information)\n");
printf(" -v|--verbose : also display modification time, age & size(B) (default 0[off])\n");
printf(" Time units:\n");
printf(" Y: Years M: Months W: Weeks D: Days\n");
printf(" h: hours m: minutes s: seconds\n");
printf(" Note: Specify Y & M with integer values. W, D, h, m & s can also take floating point values\n");
printf(" Examples of command line arguments (parsed left to right):\n");
printf(" -f /tmp # files in /tmp of any age, including future dates!\n");
printf(" -vfn -m -1M /tmp # files in /tmp modified <= 1 month ago, verbose output with ns\n");
printf(" -f -p '\\.ant$' -m 1D /tmp # files in /tmp ending in '.ant' modified >= 1 day ago\n");
printf(" -fip a /tmp -ip b /var # files named /tmp/*a*, /tmp/*A* and /var/*b*\n");
printf(" -rfa -3h src # files in the src tree accessed <= 3 hours ago\n");
printf(" -dRp ^yes -X no . # directories in . named yes* unless named *no* - reverse sort\n");
printf(" -rfM -/etc/hosts /lib # files in the /lib tree modified before /etc/hosts was\n");
printf(" -vfm -3h / /tmp -fda 1h /var # files in / and /tmp modified <= 3 hours ago, and directories\n");
printf(" # (but NOT files) in /var accessed >= 1h ago, verbose output\n");
printf(" -f -m -20201231_010203.5 . # files in . modified at or before 20201231_010203.5\n");
printf("\n");
printf("findfiles Copyright (C) 2016-2024 James S. Crook\n");
printf("This program comes with ABSOLUTELY NO WARRANTY.\n");
printf("This is free software, and you are welcome to redistribute it under certain conditions.\n");
printf("This program is licensed under the terms of the GNU General Public License as published\n");
printf("by the Free Software Foundation, either version 3 of the License, or (at your option) any\n");
printf("later version (see <http://www.gnu.org/licenses/>).\n");
}
/*******************************************************************************
Process a (file system) object - eg, a regular file, directory, symbolic
link, fifo, special file, etc. If the object's attributes satisfy the command
line arguments (i.e., the name matches the 'pattern(s)' - actually, Extended
Regular Expression(s) or ERE(s), the access xor modification time, etc. then,
this object is appended to the objectinfotable. If objectinfotable is full, its
size is dynamically increased.
*******************************************************************************/
void process_object(char *pathname) {
struct stat statinfo;
char objectname[MAXPATHLENGTH], *chptr;
time_t objecttime_s, objecttime_ns;
int idx, includeflag = 1;
Objectinfo *oldobjectinfotable;
/* extract the object name after the last '/' char */
if (((chptr=strrchr(pathname, PATHDELIMITERCHAR)) != NULL) && *(chptr+1) != '\0'){
strcpy(objectname, chptr+1);
} else {
strcpy(objectname, pathname);
}
/* if there is/are any ERE(s), loop through them all. If _all_ entries are either
* '-p match' or '-x non-match', this object is selected. If even one entry is a
* '-p non-match' or '-x match', this object is skipped. ERE(s) are checked in CLI order.
*/
for (idx=0; idx<numeres; idx++) { /* this for loop is skipped when numeres is 0 */
if (regexec(&eretable[idx].compiledere, objectname, (size_t)0, NULL, 0) != eretable[idx].matchcode) {
includeflag = 0; /* -p non-match or -x match: skip this object */
break; /* no need to check any later ERE(s) */
}
}
if (includeflag) {
if (lstat(pathname, &statinfo) == -1) {
fprintf(stderr, "W: process_object: Cannot access '%s'\n", pathname);
returncode = 1;
return;
}
if (accesstimeflag) {
objecttime_s = statinfo.st_atime;
objecttime_ns = statinfo.st_atim.tv_nsec;
} else {
objecttime_s = statinfo.st_mtime;
objecttime_ns = statinfo.st_mtim.tv_nsec;
}
if ((targettime_s == DEFAULTAGE && targettime_ns == DEFAULTAGE) ||
( newerthantargetflag && (
objecttime_s > targettime_s ||
(objecttime_s == targettime_s && objecttime_ns >= targettime_ns))
) ||
(!newerthantargetflag && (
objecttime_s < targettime_s ||
(objecttime_s == targettime_s && objecttime_ns <= targettime_ns))
)
) {
if (numobjsfound >= maxnumberobjects) {
if (maxnumberobjects <= MAXNUMOBJSMLTLIM) {
maxnumberobjects *= MAXNUMOBJSMLTFCT;
} else {
maxnumberobjects += MAXNUMOBJSINCVAL;
}
oldobjectinfotable = objectinfotable;
if ((objectinfotable=realloc(objectinfotable, maxnumberobjects*sizeof(Objectinfo))) == NULL) {
perror("E: insufficient memory - realloc failed");
free(oldobjectinfotable); /* Only here to make Cppcheck happy */
exit(1);
}
}
if ((objectinfotable[numobjsfound].name=malloc(strlen(pathname)+1)) == NULL) {
perror("E: insufficient memory - malloc failed");
exit(1);
}
strcpy(objectinfotable[numobjsfound].name, pathname);
objectinfotable[numobjsfound].size = statinfo.st_size;
objectinfotable[numobjsfound].type = statinfo.st_mode;
objectinfotable[numobjsfound].time_s = objecttime_s;
objectinfotable[numobjsfound].time_ns = objecttime_ns;
numobjsfound++;
}
}
}
/*******************************************************************************
Strip any trailing '/' character(s) from pathname. This function is called when
processing a directory or a symbolic link to a directory at recursion level 0
(so, specified on the command line). It seems that many *NIX commands process
'dirsymlink' and 'dirsymlink/' differently. When processing symbolic links to
directories, there are two cases:
1. When -L is not specified (the default): findfiles reproduces the behavior of
find (et al). A command line symbolic link target WITH a trailing slash ('/')
(eg, 'dirsymlink/') is followed, but when such a target has no trailing
slash (eg, 'dirsymlink') the symbolic link is NOT followed.
2. When -L is specified: findfiles always follows all symbolic links.
In the words of one of my old project managers at the Cupertino HP UNIX lab in
1987, "standard is better than better". I still don't like it, though.
*******************************************************************************/
void trim_trailing_slashes(char *pathname) {
char *chptr;
int len;
len = strlen(pathname);
if (len > 1) { /* handle the special case of '/' correctly */
chptr = pathname+len-1;
while (chptr >= pathname && *chptr == PATHDELIMITERCHAR) {
*chptr-- = '\0';
}
}
}
/*******************************************************************************
Very large numbers can be difficult to read - especially when they have no
thousands separators. This function displays object sizes with a suitably scaled
decimal part (a "mantissa" of sorts) and a suitable unit (eg, "GiB"). For
example, an object of size 1000000000B is displayed as "1.00MB" or "954MiB".
*******************************************************************************/
#define TENLIMIT 9.9999 /* Prevent printf rounding issues with 10 */
#define HUNDREDLIMIT 99.999 /* Prevent printf rounding issues with 100 */
void display_human_readable_size(size_t size) {
float mantissa;
size_t unitidx, divisor = 1;
/* Loop from index 0 to (max) N-2. That is, max N-1 interations! */
for (unitidx=0; unitidx<numhumanunits-1; unitidx++) {
if (size < humanunittable[unitidx].sizelimit) {
break;
} else {
divisor = humanunittable[unitidx].sizelimit;
}
}
/* Use the largest unit's details even if the file size exceeds its sizelimit */
mantissa = size / (float)divisor;
if (mantissa < TENLIMIT) {
printf(" %4.2f%s ", mantissa, humanunittable[unitidx].unitstring);
} else if (mantissa < HUNDREDLIMIT) {
printf(" %4.1f%s ", mantissa, humanunittable[unitidx].unitstring);
} else {
printf(" %4.0f%s ", mantissa, humanunittable[unitidx].unitstring);
}
}
/*******************************************************************************
Process a (file system) pathname (a file, directory or "other" object).
*******************************************************************************/
void process_path(char *pathname, int recursiondepth) {
struct stat statinfo;
if (!regularfileflag && !directoryflag && !otherobjectflag) {
fprintf(stderr, "W: No output target types requested for '%s'!\n",pathname);
returncode = 1;
return;
}
if (lstat(pathname, &statinfo) == -1) {
fprintf(stderr, "W: process_path: Cannot access '%s'\n", pathname);
returncode = 1;
return;
}
if (S_ISREG(statinfo.st_mode)) { /* process a "regular" file */
if (regularfileflag) {
process_object(pathname);
}
/* process a directory or symlink to a directory if followsymlinksflag is set */
} else if (S_ISDIR(statinfo.st_mode) || (S_ISLNK(statinfo.st_mode) && followsymlinksflag)) {
if (recursiondepth == 0) {
trim_trailing_slashes(pathname);
}
if (directoryflag) {
process_object(pathname);
}
/* Is this a command line argument (directory or symlink/) AND maxrecursiondepth > 0 */
if (recursiondepth == 0 && maxrecursiondepth > 0) {
process_directory(pathname, recursiondepth);
} else if (recursiveflag) {
if (recursiondepth < maxrecursiondepth) {
process_directory(pathname, recursiondepth);
} else if (verbosity > 1) {
fprintf(stderr, "W: Traversing directory '%s' (depth %d) would exceed max depth of %d\n",
pathname, recursiondepth, maxrecursiondepth);
}
}
} else if (otherobjectflag) { /* process "other" object types */
process_object(pathname);
}
}
/*******************************************************************************
Process a directory. Open it, read all it's entries (objects) and call
process_path for each one (EXCEPT '.' and '..') and close it.
*******************************************************************************/
void process_directory(char *pathname, int recursiondepth) {
DIR *dirptr;
struct dirent *direntptr;
char newpathname[MAXPATHLENGTH];
char dirpath[MAXPATHLENGTH];
if (strcmp(pathname, "/")) {
sprintf(dirpath, "%s%c", pathname, PATHDELIMITERCHAR); /* not "/" */
} else {
sprintf(dirpath, "%c", PATHDELIMITERCHAR); /* pathname is "/" */
}
if ((dirptr=opendir(pathname)) == (DIR*)NULL) {
fprintf(stderr, "W: opendir error - ");
perror(pathname);
returncode = 1;
return;
}
while ((direntptr=readdir(dirptr)) != (struct dirent *)NULL) {
if (strcmp(direntptr->d_name, ".") && strcmp(direntptr->d_name, "..")) {
/* create newpathname from pathname/objectname. (sprintf generates a gcc warning) */
strcpy(newpathname, dirpath);
strcat(newpathname, direntptr->d_name);
process_path(newpathname, recursiondepth+1);
}
}
if (closedir(dirptr)) {
perror(pathname);
returncode = 1;
}
}
/*******************************************************************************
Comparison function for sorting objectinfotable by time (with qsort). The sort
order is: seconds, then nanoseconds, then filename.
*******************************************************************************/
int compare_object_info(const void *firstptr, const void *secondptr) {
const Objectinfo *firstobjinfoptr = firstptr; /* to keep gcc happy */
const Objectinfo *secondobjinfoptr = secondptr;
if (firstobjinfoptr->time_s != secondobjinfoptr->time_s) {
return (secondobjinfoptr->time_s - firstobjinfoptr->time_s)*sortmultiplier;
} else {
if (firstobjinfoptr->time_ns != secondobjinfoptr->time_ns) {
return (secondobjinfoptr->time_ns - firstobjinfoptr->time_ns)*sortmultiplier;
} else {
return (strcmp(firstobjinfoptr->name, secondobjinfoptr->name))*sortmultiplier;
}
}
}
/*******************************************************************************
Sort objectinfotable by time, and display the object's information - optionally,
the timestamp and age, and (always) the name. Due to storing times in two
variables (*_s and *_ns), it is necessary to add 1s to the objectage_ns value and
subtract 1s from the objectage_s value whenever starttime_ns < the_object's_age_in_ns.
*******************************************************************************/
void list_objects() {
struct tm *localtimeinfoptr;
char objectagestr[MAXOBJAGESTRLEN], *chptr;
int foundidx, negativeageflag;
time_t objectage_s, objectage_ns, absobjectage_s, days, hrs, mins, secs;
qsort((void*)objectinfotable, (size_t)numobjsfound, (size_t)sizeof(Objectinfo), compare_object_info);
for (foundidx=0; foundidx<numobjsfound; foundidx++) {
if (verbosity > 0) {
if (verbosity > 2) { /* Test/debug: object time in s and ns */
printf("%10ld.%09ld = ", objectinfotable[foundidx].time_s,
objectinfotable[foundidx].time_ns);
}
/* year, month day, hour, minute, second */
localtimeinfoptr = localtime(&objectinfotable[foundidx].time_s);
printf(datetimeformatstr, localtimeinfoptr->tm_year+TMBASEYEAR,
localtimeinfoptr->tm_mon+TMBASEMONTH, localtimeinfoptr->tm_mday,
localtimeinfoptr->tm_hour, localtimeinfoptr->tm_min, localtimeinfoptr->tm_sec);
if (displaynsecflag) { /* ns */
printf(".%09ld", objectinfotable[foundidx].time_ns);
}
if (starttime_s > objectinfotable[foundidx].time_s || /* starttime >= object's time */
(starttime_s == objectinfotable[foundidx].time_s &&
starttime_ns >= objectinfotable[foundidx].time_ns)) {
objectage_s = starttime_s - objectinfotable[foundidx].time_s;
if (starttime_ns >= objectinfotable[foundidx].time_ns) {
objectage_ns = starttime_ns - objectinfotable[foundidx].time_ns;
} else {
objectage_ns = starttime_ns - objectinfotable[foundidx].time_ns + NANOSECONDSPERSECOND;
objectage_s--;
}
negativeageflag = 0;
} else { /* object's time is after starttime - future! */
objectage_s = starttime_s - objectinfotable[foundidx].time_s;
if (starttime_ns <= objectinfotable[foundidx].time_ns) {
objectage_ns = objectinfotable[foundidx].time_ns - starttime_ns;
} else {
objectage_ns = objectinfotable[foundidx].time_ns - starttime_ns + NANOSECONDSPERSECOND;
objectage_s++;
}
negativeageflag = 1;
}
if (verbosity > 2) { /* Test/debug: object age in s and ns */
printf(" %10ld.%09ld = ", objectage_s, objectage_ns);
}
if (displaysecondsflag) { /* object age in seconds */
printf("%16ld", objectage_s);
if (displaynsecflag) {
printf(".%09ld", objectage_ns);
}
printf("%c ", secondsunitchar);
} else { /* object age in days, hours, minutes and seconds */
absobjectage_s = objectage_s >= 0 ? objectage_s : -objectage_s; /* absolute value */
days = absobjectage_s/SECONDSPERDAY;
hrs = absobjectage_s/SECONDSPERHOUR - days*HOURSPERDAY;
mins = absobjectage_s/SECONDSPERMINUTE - days*MINUTESPERDAY - hrs*MINUTESPERHOUR;
secs = absobjectage_s % SECONDSPERMINUTE;
sprintf(objectagestr, ageformatstr, days, hrs, mins, secs);
/* if objectage_s is negative (future timestamp), display a - sign */
if (negativeageflag) {
if ((chptr=strrchr(objectagestr, ' ')) != NULL) {
*chptr = NEGATIVESIGNCHAR; /* %07ld : OK for 999999 days - until the year 4707 */
} else {
fprintf(stderr, "E: Insufficient 'days' field width in '%s'\n", ageformatstr);
exit(1);
}
}
printf("%s", objectagestr);
if (displaynsecflag) {
printf(".%09ld", objectage_ns);
}
printf(" ");
}
if (humanunittable == NULL) {
printf(" %14lu%c ", objectinfotable[foundidx].size, bytesunitchar);
} else {
display_human_readable_size(objectinfotable[foundidx].size);
}
}
if (displaytypesflag) { /* In order of expected frequencey (first 3, anyway) */
if (S_ISREG( objectinfotable[foundidx].type)) printf("Fil ");
else if (S_ISDIR( objectinfotable[foundidx].type)) printf("Dir ");
else if (S_ISLNK( objectinfotable[foundidx].type)) printf("Sln ");
else if (S_ISBLK( objectinfotable[foundidx].type)) printf("Blk ");
else if (S_ISCHR( objectinfotable[foundidx].type)) printf("Chr ");
else if (S_ISFIFO(objectinfotable[foundidx].type)) printf("FIF ");
else if (S_ISSOCK(objectinfotable[foundidx].type)) printf("Soc ");
else printf("Oth ");
}
printf("%s\n", objectinfotable[foundidx].name);
}
if (numtargets == 0 && verbosity > 1) {
fprintf(stderr, "W: No targets were specified on the command line!\n");
}
}
/*******************************************************************************
Integer values must be used with units 'M' (months) and 'Y' (years) because
months and years vary in size. E.g., '0.5M' does NOT always equate to the same
amount of time, but 1.33s, 0.25h, 0.5m, 0.1W, etc., all do.
*******************************************************************************/
void check_integer(char *relativeagestr) {
char *chptr;
for (chptr=relativeagestr; chptr<relativeagestr+strlen(relativeagestr)-1; chptr++) {
if (!isdigit(*chptr) && *chptr != NEGATIVESIGNCHAR && *chptr != POSITIVESIGNCHAR ) {
fprintf(stderr, "W: non-integer character '%c' in '%s'!\n", *chptr, relativeagestr);
}
}
}
/*******************************************************************************
Convert a (hopefully numeric) string to the equivalent number of nanoseconds (ns).
Eg "123" to 123000000, "000123" to 123000 and "000000123" to 123.
*******************************************************************************/
int convert_string_to_ns(char *fractionstr) {
char nanosecondsstr[] = NANOSECONDSSTR;
const char *fromchptr = fractionstr;
char *tochptr = nanosecondsstr+1;
while (*fromchptr && *tochptr) {
if (isdigit(*fromchptr)) {
*tochptr++ = *fromchptr++;
} else {
fprintf(stderr, "E: Illegal character ('%c') in time fraction string '%s'\n", *fromchptr, fractionstr);
exit(1);
}
}
return atoi(nanosecondsstr) - NANOSECONDSPERSECOND;
}
/*******************************************************************************
Adjust the relative age of targettime when called with units of seconds. The
reason for not using adjust_relative_age (below) is because when that function
is called with a large integer (s), the fraction (ns) is sometimes rounded.
See below. This function returns the correct number of nanoseconds.
*******************************************************************************/
time_t adjust_relative_age_seconds(const char *relativeagestr, int *timeunitptr) {
char *decimalchptr, trimmedrelativeagestr[sizeof(NANOSECONDSSTR)-1] = { '\0' };
int trimmedrelativeagestrlen, relativeage_ns = 0;
*timeunitptr -= atoi(relativeagestr); /* relativeage_s: integer */
if ((decimalchptr=strchr(relativeagestr, DECIMALSEPARATORCHAR)) != NULL) { /* fraction */
/* copy only up to 9 chars. (trimmedrelativeagestr is initialized to all '\0' chars) */
strncpy(trimmedrelativeagestr, decimalchptr+1, sizeof(NANOSECONDSSTR)-2);
/* if the last character is 's' (seconds), remove it from trimmedrelativeagestr */
trimmedrelativeagestrlen = strlen(trimmedrelativeagestr);
if (trimmedrelativeagestr[trimmedrelativeagestrlen-1] == 's') {
trimmedrelativeagestr[trimmedrelativeagestrlen-1] = '\0';
}
}
relativeage_ns = convert_string_to_ns(trimmedrelativeagestr);
return relativeage_ns;
}
/*******************************************************************************
Adjust the relative age of targettime. Parse the relativeagestr argument (a
string representing floating point number) into integer and (optional) fraction
parts. Update the breakdown time structure (timeinfoptr, see below) by the
calculated integer number of seconds, and return the calculated number of ns.
*******************************************************************************/
time_t adjust_relative_age(const char *relativeagestr, int *timeunitptr, long secsperunit) {
double relativeage_s, relativeage_ns;
relativeage_s = atof(relativeagestr) * secsperunit;
relativeage_ns = relativeage_s - (long)relativeage_s;
*timeunitptr -= (int)relativeage_s;
return (time_t)(relativeage_ns * NANOSECONDSPERSECOND);
}
/*******************************************************************************
Set the targettime (_s and _ns) relative to the start time. For example, -m 2D
for 2 days ago or -a -10m for 10 minutes ago.
*******************************************************************************/
void set_relative_targettime(char *timeinfostr, struct tm *timeinfoptr, char timeunitchar) {
time_t relativeage_ns = DEFAULTAGE;
switch (timeunitchar) { /* set targettime relative to now */
case 's': relativeage_ns = adjust_relative_age_seconds(timeinfostr, &(timeinfoptr->tm_sec));
break;
case 'm': relativeage_ns = adjust_relative_age(timeinfostr, &(timeinfoptr->tm_sec), SECONDSPERMINUTE);
break;
case 'h': relativeage_ns = adjust_relative_age(timeinfostr, &(timeinfoptr->tm_sec), SECONDSPERHOUR);
break;
case 'D': relativeage_ns = adjust_relative_age(timeinfostr, &(timeinfoptr->tm_sec), SECONDSPERDAY);
break;
case 'W': relativeage_ns = adjust_relative_age(timeinfostr, &(timeinfoptr->tm_sec), SECONDSPERWEEK);
break;
case 'M': check_integer(timeinfostr);
timeinfoptr->tm_mon -= atoi(timeinfostr);
break;
case 'Y': check_integer(timeinfostr);
timeinfoptr->tm_year -= atoi(timeinfostr);
break;
default: fprintf(stderr, "E: Illegal time unit '%c'\n", timeunitchar);
exit(1);
}
targettime_s = mktime(timeinfoptr);
/* Due to storing times in two variables (_s and _ns), it is necessary to add 1s to
the targettime_ns value and subtract 1s from the targettime_s value whenever
starttime_ns < relativeage_ns. */
if (starttime_ns >= relativeage_ns) {
targettime_ns = starttime_ns - relativeage_ns;
} else {
targettime_ns = starttime_ns - relativeage_ns + NANOSECONDSPERSECOND;
targettime_s--;
}
}
/*******************************************************************************
Convert a time in seconds to a locale-specific (language, TZ) date string.
*******************************************************************************/
void convert_time_s_to_date_string(time_t time_s, char *datestr) {
struct tm timeinfo;
localtime_r(&time_s, &timeinfo);
strftime(datestr, MAXDATESTRLENGTH, infodatetimeformatstr, &timeinfo);
}
/*******************************************************************************
Display the starttime in s.ns in a more easily readable format.
*******************************************************************************/
void list_starttime() {
char datestr[MAXDATESTRLENGTH];
convert_time_s_to_date_string(starttime_s, datestr);
fprintf(stderr, "i: start time: %15ld.%09lds ~= %s\n", starttime_s, starttime_ns, datestr);
fflush(stderr);
}
/*******************************************************************************
Convert a text input string (representing a timestamp) into a time since the
Epoch (s in *time_s_ptr and ns in *time_ns_ptr).
*******************************************************************************/
void convert_text_time_to_s_and_ns(char *timeinfostr, char *formatstr, struct tm *timeinfoptr, time_t *time_s_ptr, time_t *time_ns_ptr) {
size_t timestampformatstrlen;
char *decimalchptr;
*time_ns_ptr = DEFAULTAGE; /* set to zero unless a fraction of a second is specified */
/* convert the command line timeinfostr (eg, YYYYMMDD_HHMMSS) to a breakdown time structure (struct tm)
and return a pointer to anything after the allowed format (formatstr), if any (which should be a decimal
fraction of a second - eg, ".25" */
decimalchptr = strptime(timeinfostr, formatstr, timeinfoptr);
timeinfoptr->tm_isdst = -1; /* set is_dst to -1 so mktime will try to determine whether DST is in effect */
*time_s_ptr = mktime(timeinfoptr); /* convert the breakdowns time structure to the number of seconds */
if (decimalchptr == NULL) {
fprintf(stderr, "E: bad timestamp: '%s' must be in format '%s[.ns]'\n", timeinfostr, formatstr);
exit(1);
} else if (*decimalchptr == DECIMALSEPARATORCHAR) { /* if a DECIMALSEPARATORCHAR (decimal point) follows a valid timestamp */
/* Ensure that the last characters of formatstr (eg, %Y%m%d_%H%M%S) are SECONDSFORMATSTR ("%S") */
timestampformatstrlen = strlen(formatstr);
if (timestampformatstrlen < sizeof(SECONDSFORMATSTR)-1 ||
strcmp(formatstr+timestampformatstrlen-(sizeof(SECONDSFORMATSTR)-1), SECONDSFORMATSTR)) {
fprintf(stderr, "E: last two characters of '%s' must be '%s' when using fractions of seconds\n",
formatstr, SECONDSFORMATSTR);
exit(1);
}
*time_ns_ptr = convert_string_to_ns(decimalchptr+1);
} else if (*decimalchptr != '\0') {
fprintf(stderr, "E: Illegal timestamp character(s) starting at '%c' in timestamp '%s'\n",
*decimalchptr, timeinfostr);
exit(1);
}
}
/*******************************************************************************
Set targettime from a command line argumet in one of two formats:
1. When the last character of timeinfostr is one of Y, M, D, h, m or s: by
subtracting the relative) "age" command line argument, e.g., "15D" from
starttime. Note: targettime will always be less than starttime. I.e., "-30s"
and "[+]30s" both result in targettime = starttime-30s.
2. When the last character of timeinfostr is a digit: by using timeinfostr
as a timestamp. timestampformatstr (default "%Y%m%d_%H%M%S", and configurable
with FF_TIMESTAMPFORMAT) is used to parse the value. So timestamps must be
entered in the format YYYYMMDD_HHMMSS[.secondfraction] unless the environment
variable FF_TIMESTAMPFORMAT is changed.
In either case, a first character of '-' is used to set the newerthantargetflag.
This function is called for both last access time and last modification time.
*******************************************************************************/
void set_target_time_by_cmd_line_arg(char *timeinfostr, char c) {
char timeunitchar;
struct tm timeinfo;
char datestr[MAXDATESTRLENGTH];
time_t relativeage_s, relativeage_ns;
if (c == MODTIMEINFOCHAR) {
accesstimeflag = 0;
} else {
accesstimeflag = 1;
}
timeunitchar = *(timeinfostr+strlen(timeinfostr+1));
localtime_r(&starttime_s, &timeinfo);
if (isdigit(timeunitchar) || timeunitchar == DECIMALSEPARATORCHAR) { /* last character of timeinfostr is a digit or '.' */
/*
Set the absolute time - for both (-m) modification and (-a) access - based on the
required format. The default is '%Y%m%d_%H%M%S', but this can be changed by by setting
FF_TIMESTAMPFORMAT. It is possible to specify a subset of these. If not all of year to
second are specified, the values of the start time are used to fill the missing values.
For example, one could set FF_TIMESTAMPFORMAT to 'date:%m%d, hour:%H' and specify only
'date:', month, day, ', hour:', and hour, e.g., 'date:1231, hour:23' - or '%d%m%H' and
'311223'). See strptime for details. */
if (*timeinfostr == NEGATIVESIGNCHAR ) {
newerthantargetflag = 0;
timeinfostr++;
} else {
newerthantargetflag = 1;
if (*timeinfostr == POSITIVESIGNCHAR ) {
timeinfostr++;
}
}
convert_text_time_to_s_and_ns(timeinfostr, timestampformatstr, &timeinfo, &targettime_s, &targettime_ns);
} else { /* relative age */
if (*timeinfostr == NEGATIVESIGNCHAR ) {
/* eg, (-m) '-15D' find objects modified <= 15 days ago (newer than) */
newerthantargetflag = 1;
timeinfostr++;
} else {
/* eg, (-a) '[+]15D' find objects accessed >= 15 days ago (older than) */
newerthantargetflag = 0;
}
set_relative_targettime(timeinfostr, &timeinfo, timeunitchar);
}
if (verbosity > 1) {
if (targettime_s <= starttime_s) { /* ('normal') targettime is before startttime */
if (targettime_ns <= starttime_ns) {
relativeage_s = starttime_s - targettime_s;
relativeage_ns = starttime_ns - targettime_ns;
} else {
relativeage_s = starttime_s - targettime_s - 1;
relativeage_ns = starttime_ns - targettime_ns + NANOSECONDSPERSECOND;
}
} else { /* (future) targettime is after starttime */
if (targettime_ns <= starttime_ns) {
relativeage_s = starttime_s - targettime_s + 1;
relativeage_ns = NANOSECONDSPERSECOND - (starttime_ns - targettime_ns);
} else {
relativeage_s = starttime_s - targettime_s;
relativeage_ns = targettime_ns - starttime_ns;
}
}
convert_time_s_to_date_string(targettime_s, datestr);
fprintf(stderr, "i: target time: %15ld.%09lds ~= %s\n", targettime_s, targettime_ns, datestr);
fprintf(stderr, "i: %13.5fD ~= %10ld.%09lds last %s %s target time ('%s')\n",
(float)(starttime_s-targettime_s)/SECONDSPERDAY, relativeage_s,
relativeage_ns, accesstimeflag ? "accessed" : "modified",
newerthantargetflag ? "after (newer than)" : "before (older than)", timeinfostr);
list_starttime();
fflush(stderr);
}
}
/*******************************************************************************
Set targettime to be the same as that of the reference object's last modification
or last access time, as required.
*******************************************************************************/
void set_target_time_by_object_time(char *targetobjectstr, char c) {
struct stat statinfo;
char datestr[MAXDATESTRLENGTH];
if (*targetobjectstr == NEGATIVESIGNCHAR) {
/* eg, "-M -foo" find objects last modified BEFORE foo was (OLDER than) */
newerthantargetflag = 0;
targetobjectstr++;
} else {
/* eg, "-M [+]foo" find objects last modified AFTER foo was (OLDER than) */
newerthantargetflag = 1;
if (*targetobjectstr == POSITIVESIGNCHAR) {
targetobjectstr++;
}
}
if (*targetobjectstr && (lstat(targetobjectstr, &statinfo) != -1)) {
if (c == REFMODTIMECHAR) {
accesstimeflag = 0;
targettime_s = statinfo.st_mtime;
targettime_ns = statinfo.st_mtim.tv_nsec;
} else {
accesstimeflag = 1;
targettime_s = statinfo.st_atime;
targettime_ns = statinfo.st_atim.tv_nsec;
}
} else {
fprintf(stderr, "E: Cannot access '%s'\n", targetobjectstr);
exit(1);
}
if (verbosity > 1) {
fprintf(stderr, "i: last %s %s than '%s'\n", accesstimeflag ? "accessed" : "modified",
newerthantargetflag ? "after (newer than)" : "before (older than)", targetobjectstr);
convert_time_s_to_date_string(targettime_s, datestr);
fprintf(stderr, "i: target time: %15ld.%09lds ~= %s\n", targettime_s, targettime_ns, datestr);
list_starttime();
fflush(stderr);
}
if (newerthantargetflag) {
if (targettime_ns < NANOSECONDSPERSECOND-1) { /* +1ns for NEWER than (NOT the same age!) */
targettime_ns += 1;
} else { /* eg, 340.999999999 -> 341.000000000 */
targettime_s += 1;
targettime_ns = 0;
}
} else { /* -1ns for OLDER than (NOT the same age!) */
if (targettime_ns != 0) {
targettime_ns -= 1;
} else { /* eg, 341.000000000 -> 340.999999999 */
targettime_s -= 1;
targettime_ns = NANOSECONDSPERSECOND-1;
}
}
}