1 /*
2 *
3 * CLEX File Manager
4 *
5 * Copyright (C) 2001-2018 Vlado Potisk <vlado_potisk@clex.sk>
6 *
7 * CLEX is free software without warranty of any kind; see the
8 * GNU General Public License as set out in the "COPYING" document
9 * which accompanies the CLEX File Manager package.
10 *
11 * CLEX can be downloaded from http://www.clex.sk
12 *
13 */
14
15 /* comparing the contents of two directories (filepanels) */
16
17 #include "clexheaders.h"
18
19 #include <sys/stat.h> /* stat() */
20 #include <errno.h> /* errno */
21 #include <fcntl.h> /* open() */
22 #include <stdarg.h> /* log.h */
23 #include <stdlib.h> /* qsort() */
24 #include <string.h> /* strcmp() */
25 #include <unistd.h> /* close() */
26
27 #include "select.h"
28
29 #include "inout.h" /* win_waitmsg() */
30 #include "list.h" /* list_both_directories() */
31 #include "log.h" /* msgout() */
32 #include "opt.h" /* opt_changed() */
33 #include "panel.h" /* pan_adjust() */
34 #include "signals.h" /* signal_ctrlc_on() */
35 #include "util.h" /* pathname_join() */
36
37 int
cmp_prepare(void)38 cmp_prepare(void)
39 {
40 if (strcmp(USTR(ppanel_file->dir),USTR(ppanel_file->other->dir)) == 0) {
41 msgout(MSG_i,"COMPARE: same directory in both panels");
42 return -1;
43 }
44
45 panel_cmp.pd->top = panel_fopt.pd->min;
46 panel_cmp.pd->curs = panel_cmp.pd->cnt - 1; /* last line */
47 panel = panel_cmp.pd;
48 pan_adjust(panel);
49 textline = 0;
50
51 return 0;
52 }
53
54 int
cmp_summary_prepare(void)55 cmp_summary_prepare(void)
56 {
57 panel_cmp_sum.pd->top = panel_cmp_sum.pd->curs = panel_cmp_sum.pd->min;
58 panel_cmp_sum.pd->cnt = panel_cmp_sum.errors ? 6 : 5;
59 panel = panel_cmp_sum.pd;
60 textline = 0;
61
62 if (panel_cmp_sum.errors)
63 win_sethelp(HELPMSG_BASE,L"Error messages can be found in the log (alt-L)");
64
65 return 0;
66 }
67
68 /* write options to a string */
69 const char *
cmp_saveopt(void)70 cmp_saveopt(void)
71 {
72 int i, j;
73 static char buff[CMP_TOTAL_ + 1];
74
75 for (i = j = 0; i < CMP_TOTAL_; i++)
76 if (COPT(i))
77 buff[j++] = 'A' + i;
78 buff[j] = '\0';
79
80 return buff;
81 }
82
83 /* read options from a string */
84 int
cmp_restoreopt(const char * opt)85 cmp_restoreopt(const char *opt)
86 {
87 int i;
88 unsigned char ch;
89
90 for (i = 0; i < CMP_TOTAL_; i++)
91 COPT(i) = 0;
92
93 while ( (ch = *opt++) ) {
94 if (ch < 'A' || ch >= 'A' + CMP_TOTAL_)
95 return -1;
96 COPT(ch - 'A') = 1;
97 }
98
99 return 0;
100 }
101
102 static int
qcmp(const void * e1,const void * e2)103 qcmp(const void *e1, const void *e2)
104 {
105 return strcmp( /* not strcoll() */
106 SDSTR((*(FILE_ENTRY **)e1)->file),
107 SDSTR((*(FILE_ENTRY **)e2)->file));
108 }
109
110 #define CMP_BUF_STR 16384
111
112 /* return value: -1 error, 0 compare ok, +1 compare failed */
113 static int
data_cmp(int fd1,const char * file1,int fd2,const char * file2)114 data_cmp(int fd1, const char *file1, int fd2, const char *file2)
115 {
116 struct stat st1, st2;
117 static char buff1[CMP_BUF_STR], buff2[CMP_BUF_STR];
118 off_t filesize;
119 size_t chunksize;
120
121 if (fstat(fd1,&st1) < 0 || !S_ISREG(st1.st_mode)) {
122 msgout(MSG_NOTICE,"COMPARE: File \"./%s\" is not a regular file",file1);
123 return -1;
124 }
125 if (fstat(fd2,&st2) < 0 || !S_ISREG(st2.st_mode)) {
126 msgout(MSG_NOTICE,"COMPARE: File \"%s\" is not a regular file",file2);
127 return -1;
128 }
129 if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino)
130 /* same file */
131 return 0;
132 if ((filesize = st1.st_size) != st2.st_size)
133 return 1;
134
135 while (filesize > 0) {
136 chunksize = filesize > CMP_BUF_STR ? CMP_BUF_STR : filesize;
137 if (ctrlc_flag)
138 return -1;
139 if (read_fd(fd1,buff1,chunksize) != chunksize) {
140 msgout(MSG_NOTICE,"COMPARE: Cannot read from \"./%s\" (%s)",file1,strerror(errno));
141 return -1;
142 }
143 if (read_fd(fd2,buff2,chunksize) != chunksize) {
144 msgout(MSG_NOTICE,"COMPARE: Cannot read from \"%s\" (%s)",file2,strerror(errno));
145 return -1;
146 }
147 if (memcmp(buff1,buff2,chunksize) != 0)
148 return 1;
149 filesize -= chunksize;
150 }
151
152 return 0;
153 }
154
155 /* return value: -1 error, 0 compare ok, +1 compare failed */
156 static int
file_cmp(const char * file1,const char * file2)157 file_cmp(const char *file1, const char *file2)
158 {
159 int cmp, fd1, fd2;
160
161 fd1 = open(file1,O_RDONLY | O_NONBLOCK);
162 if (fd1 < 0) {
163 msgout(MSG_NOTICE,"COMPARE: Cannot open \"./%s\" (%s)",file1,strerror(errno));
164 return -1;
165 }
166 fd2 = open(file2,O_RDONLY | O_NONBLOCK);
167 if (fd2 < 0) {
168 msgout(MSG_NOTICE,"COMPARE: Cannot open \"%s\" (%s)",file2,strerror(errno));
169 close(fd1);
170 return -1;
171 }
172 cmp = data_cmp(fd1,file1,fd2,file2);
173 close(fd1);
174 close(fd2);
175 return cmp;
176 }
177
178 static void
cmp_directories(void)179 cmp_directories(void)
180 {
181 int min, med, max, cmp, i, j, cnt1, selcnt1, selcnt2;
182 const char *name2;
183 FILE_ENTRY *pfe1, *pfe2;
184 static FILE_ENTRY **p1 = 0; /* copy of panel #1 sorted for binary search */
185 static int p1_alloc = 0;
186
187 /*
188 * - select all files and both panels
189 * - for each file from panel #2:
190 * - find a matching file from panel #1
191 * - if a pair is found, compare them according to the selected options
192 * - if the compared files are equal, deselect them
193 */
194
195 panel_cmp_sum.errors = panel_cmp_sum.names = panel_cmp_sum.equal = 0;
196
197 /* reread panels */
198 list_both_directories();
199
200 ctrlc_flag = 0;
201 if (COPT(CMP_DATA)) { /* going to compare data */
202 signal_ctrlc_on();
203 win_waitmsg();
204 pathname_set_directory(USTR(ppanel_file->other->dir));
205 }
206
207 if (COPT(CMP_REGULAR)) {
208 for (cnt1 = i = 0; i < ppanel_file->pd->cnt; i++) {
209 pfe1 = ppanel_file->files[i];
210 if ( (pfe1->select = IS_FT_PLAIN(pfe1->file_type)) )
211 cnt1++;
212 }
213 panel_cmp_sum.nonreg1 = ppanel_file->pd->cnt - cnt1;
214 }
215 else {
216 cnt1 = ppanel_file->pd->cnt;
217 panel_cmp_sum.nonreg1 = 0;
218 }
219 selcnt1 = cnt1;
220
221 if (cnt1) {
222 if (p1_alloc < cnt1) {
223 efree(p1);
224 p1_alloc = cnt1;
225 p1 = emalloc(p1_alloc * sizeof(FILE_ENTRY *));
226 }
227
228 if (COPT(CMP_REGULAR))
229 for (i = j = 0; i < ppanel_file->pd->cnt; i++) {
230 pfe1 = ppanel_file->files[i];
231 if (pfe1->select)
232 p1[j++] = pfe1;
233 }
234 else
235 for (i = 0; i < ppanel_file->pd->cnt; i++) {
236 pfe1 = p1[i] = ppanel_file->files[i];
237 pfe1->select = 1;
238 }
239
240 qsort(p1,cnt1,sizeof(FILE_ENTRY *),qcmp);
241 }
242
243 panel_cmp_sum.nonreg2 = 0;
244 selcnt2 = 0;
245 for (i = 0; i < ppanel_file->other->pd->cnt; i++) {
246 pfe2 = ppanel_file->other->files[i];
247 if ( !(pfe2->select = !COPT(CMP_REGULAR) || IS_FT_PLAIN(pfe2->file_type)) ) {
248 panel_cmp_sum.nonreg2++;
249 continue;
250 }
251 selcnt2++;
252
253 if (panel_cmp_sum.names == cnt1)
254 /* we have seen all files from panel#1 */
255 continue; /* not break */
256
257 name2 = SDSTR(pfe2->file);
258 for (pfe1 = 0, min = 0, max = cnt1 - 1; min <= max; ) {
259 med = (min + max) / 2;
260 cmp = strcmp(name2,SDSTR(p1[med]->file));
261 if (cmp == 0) {
262 pfe1 = p1[med];
263 /* entries *pfe1 and *pfe2 have the same name */
264 break;
265 }
266 if (cmp < 0)
267 max = med - 1;
268 else
269 min = med + 1;
270 }
271 if (pfe1 == 0 || !pfe1->select)
272 continue;
273
274 panel_cmp_sum.names++;
275
276 /* always comparing type */
277 if (pfe1->file_type == FT_NA || !(
278 (IS_FT_PLAIN(pfe1->file_type) && IS_FT_PLAIN(pfe2->file_type))
279 || (IS_FT_DIR(pfe1->file_type) && IS_FT_DIR(pfe2->file_type))
280 || (pfe1->file_type == pfe2->file_type) )
281 )
282 continue;
283 if (pfe1->symlink != pfe2->symlink)
284 continue;
285
286 /* comparing size (or device numbers) */
287 if (COPT(CMP_SIZE)
288 && ((IS_FT_DEV(pfe1->file_type) && pfe1->devnum != pfe2->devnum)
289 || (IS_FT_PLAIN(pfe1->file_type) && pfe1->size != pfe2->size)))
290 continue;
291
292 if (COPT(CMP_OWNER)
293 && (pfe1->uid != pfe2->uid || pfe1->gid != pfe2->gid))
294 continue;
295
296 if (COPT(CMP_MODE) && pfe1->mode12 != pfe2->mode12)
297 continue;
298
299 if (COPT(CMP_DATA) && IS_FT_PLAIN(pfe1->file_type)) {
300 if (pfe1->size != pfe2->size)
301 continue;
302 if ( (cmp = file_cmp(SDSTR(pfe1->file),pathname_join(name2))) ) {
303 if (ctrlc_flag)
304 break;
305 if (cmp < 0)
306 panel_cmp_sum.errors++;
307 continue;
308 }
309 }
310
311 /* pair of matching files found */
312 pfe1->select = 0;
313 selcnt1--;
314 pfe2->select = 0;
315 selcnt2--;
316 panel_cmp_sum.equal++;
317 }
318 ppanel_file->selected = selcnt1;
319 ppanel_file->other->selected = selcnt2;
320
321 if (COPT(CMP_DATA))
322 signal_ctrlc_off();
323
324 if (ctrlc_flag) {
325 msgout(MSG_i,"COMPARE: operation canceled");
326 /* clear all marks */
327 for (i = 0; i < cnt1; i++)
328 ppanel_file->files[i]->select = 0;
329 ppanel_file->selected = 0;
330 for (i = 0; i < ppanel_file->other->pd->cnt; i++)
331 ppanel_file->other->files[i]->select = 0;
332 ppanel_file->other->selected = 0;
333
334 next_mode = MODE_SPECIAL_RETURN;
335 return;
336 }
337
338 next_mode = MODE_CMP_SUM;
339 }
340
341 void
cx_cmp(void)342 cx_cmp(void)
343 {
344 int sel;
345
346 sel = panel_cmp.pd->curs;
347 if (sel < CMP_TOTAL_) {
348 TOGGLE(COPT(sel));
349 opt_changed();
350 win_panel_opt();
351 }
352 else
353 cmp_directories();
354 }
355