Directory scan now works.
[finddup.git] / finddup.c
1
2 /*
3  *  finddup is a simple utility to display the files and directories
4  *  according to their total disk occupancy.
5  *
6  *  Copyright (c) 2010 Francois Fleuret
7  *  Written by Francois Fleuret <francois@fleuret.org>
8  *
9  *  This file is part of finddup.
10  *
11  *  finddup is free software: you can redistribute it and/or modify it
12  *  under the terms of the GNU General Public License version 3 as
13  *  published by the Free Software Foundation.
14  *
15  *  finddup is distributed in the hope that it will be useful, but WITHOUT
16  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17  *  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
18  *  License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with finddup.  If not, see <http://www.gnu.org/licenses/>.
22  *
23  */
24
25 #define _BSD_SOURCE
26
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <dirent.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <unistd.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <sys/ioctl.h>
36 #include <locale.h>
37 #include <getopt.h>
38 #include <fcntl.h>
39
40 #define BUFFER_SIZE 4096
41
42 typedef int64_t size_sum_t;
43
44 /* Yeah, global variables! */
45
46 int ignore_dotfiles = 0; /* 1 means ignore files and directories
47                             starting with a dot */
48
49 int forced_width = 0; /* -1 means no width limit, strictly positive
50                          means limit, 0 means not active */
51
52 int forced_height = 0; /* -1 means no height limit, strictly positive
53                            means limit, 0 means not active */
54
55 int fancy_size_display = 0; /* 1 means to use floating values with K, M and G
56                                as units */
57
58 int reverse_sorting = 0; /* 1 means to show the large ones first */
59
60 int show_top = 0; /* 1 means to show the top of the sorted list
61                      instead of the bottom */
62
63 /********************************************************************/
64
65 /* malloc with error checking.  */
66
67 void *safe_malloc(size_t n) {
68   void *p = malloc(n);
69   if (!p && n != 0) {
70     printf("Can not allocate memory: %s\n", strerror(errno));
71     exit(EXIT_FAILURE);
72   }
73   return p;
74 }
75
76 /********************************************************************/
77
78 int ignore_entry(const char *name) {
79   return
80     strcmp(name, ".") == 0 ||
81     strcmp(name, "..") == 0 ||
82     (ignore_dotfiles && name[0] == '.');
83 }
84
85 void print_size_sum(size_sum_t s) {
86   char tmp[100];
87   char *a = tmp + sizeof(tmp)/sizeof(char);
88   *(--a) = '\0';
89   if(s) {
90     while(s) {
91       *(--a) = s%10 + '0';
92       s /= 10;
93     }
94   } else {
95     *(--a) = '0';
96   }
97   printf(a);
98 }
99
100 size_sum_t file_or_dir_size(const char *name) {
101   DIR *dir;
102   struct dirent *dir_e;
103   struct stat dummy;
104   size_sum_t result;
105   char subname[BUFFER_SIZE];
106
107   result = 0;
108
109   if(lstat(name, &dummy) != 0) {
110     printf("Can not stat %s: %s\n", name, strerror(errno));
111     exit(EXIT_FAILURE);
112   }
113
114   if(S_ISLNK(dummy.st_mode)) {
115     return 0;
116   }
117
118   dir = opendir(name);
119
120   if(dir) {
121     while((dir_e = readdir(dir))) {
122       if(!ignore_entry(dir_e->d_name)) {
123         snprintf(subname, BUFFER_SIZE, "%s/%s", name, dir_e->d_name);
124         result += file_or_dir_size(subname);
125       }
126     }
127     closedir(dir);
128   } else {
129     if(S_ISREG(dummy.st_mode)) {
130       result += dummy.st_size;
131     }
132   }
133
134   return result;
135 }
136
137 /**********************************************************************/
138
139 struct file_with_size {
140   char *filename;
141   size_t size;
142   struct file_with_size *next;
143   int error;
144 };
145
146 struct file_list {
147   struct file_with_size *head, *tail;
148 };
149
150 void create(struct file_list *list, const char *name) {
151   list->head = safe_malloc(sizeof(struct file_with_size));
152   list->tail = 0;
153   list->head->filename = strdup(name);
154   list->head->size = file_or_dir_size(name);
155   list->head->next = 0;
156 }
157
158 void destroy(struct file_with_size *node) {
159   struct file_with_size *next;
160   while(node) {
161     next = node->next;
162     free(node->filename);
163     free(node);
164     node = next;
165   }
166 }
167
168 /**********************************************************************/
169
170 int same_size(struct file_with_size *f1, struct file_with_size *f2) {
171   return f1->size == f2->size;
172 }
173
174 int same_content(struct file_with_size *f1, struct file_with_size *f2) {
175   int fd1, fd2, eq, s1, s2;
176   char buffer1[BUFFER_SIZE], buffer2[BUFFER_SIZE];
177
178   fd1 = open(f1->filename, O_RDONLY);
179   fd2 = open(f2->filename, O_RDONLY);
180   eq = 1;
181
182   if(fd1 >= 0 && fd2 >= 0) {
183     while(eq) {
184       s1 = read(fd1, buffer1, BUFFER_SIZE);
185       if(s1 < 0) {
186         f1->error = 1;
187         eq = 0;
188       }
189       s2 = read(fd2, buffer2, BUFFER_SIZE);
190       if(s2 < 0) {
191         f2->error = 1;
192         eq = 0;
193       }
194       if(s1 > 0 && s2 > 0) {
195         if(s1 == s2) {
196           eq = (strncmp(buffer1, buffer2, s1) == 0);
197         } else {
198           fprintf(stderr,
199                   "Different read size without error on files of same size.\n");
200           exit(EXIT_FAILURE);
201         }
202       }
203     }
204   }
205
206   if(fd1 >= 0) {
207     close(fd1);
208   } else {
209     f1->error = 1;
210     eq = 0;
211   }
212
213   if(fd2 >= 0) {
214     close(fd2);
215   } else {
216     f2->error = 1;
217     eq = 0;
218   }
219
220   return eq;
221 }
222
223 int same_files(struct file_with_size *f1, struct file_with_size *f2) {
224   return same_size(f1, f2) && same_content(f1, f2);
225 }
226
227 /**********************************************************************/
228
229 struct file_with_size *scan_directory(struct file_with_size *tail, const char *name) {
230   DIR *dir;
231   struct dirent *dir_e;
232   struct stat dummy;
233   struct file_with_size *tmp;
234   char subname[BUFFER_SIZE];
235
236   if(lstat(name, &dummy) != 0) {
237     fprintf(stderr, "Can not stat %s: %s\n", name, strerror(errno));
238     exit(EXIT_FAILURE);
239   }
240
241   if(S_ISLNK(dummy.st_mode)) {
242     return tail;
243   }
244
245   dir = opendir(name);
246
247   if(dir) {
248     while((dir_e = readdir(dir))) {
249       if(!ignore_entry(dir_e->d_name)) {
250         snprintf(subname, BUFFER_SIZE, "%s/%s", name, dir_e->d_name);
251         tail = scan_directory(tail, subname);
252       }
253     }
254     closedir(dir);
255   } else {
256     if(S_ISREG(dummy.st_mode)) {
257       tmp = safe_malloc(sizeof(struct file_with_size));
258       tmp->next = tail;
259       tmp->filename = strdup(name);
260       tmp->size = file_or_dir_size(name);
261       tail = tmp;
262     }
263   }
264
265   return tail;
266 }
267
268 void start(const char *dirname1, const char *dirname2) {
269   struct file_with_size *list1, *list2;
270   struct file_with_size *node;
271   list1 = scan_directory(0, dirname1);
272   list2 = scan_directory(0, dirname2);
273
274   for(node = list1; node; node = node->next) {
275     printf("list1: %s\n", node->filename);
276   }
277
278   for(node = list2; node; node = node->next) {
279     printf("list2: %s\n", node->filename);
280   }
281
282   destroy(list1);
283   destroy(list2);
284 }
285
286 /**********************************************************************/
287
288 int main(int argc, char **argv) {
289   int c;
290   struct file_with_size *root;
291
292   root = 0;
293
294   setlocale (LC_ALL, "");
295
296   while (1) {
297     c = getopt(argc, argv, "h");
298     if (c == -1)
299       break;
300
301     switch (c) {
302
303     case 'h':
304       printf("Usage: finddup [OPTION]... [FILE]...\n");
305       printf("Report bugs and comments to <francois@fleuret.org>\n");
306       exit(EXIT_SUCCESS);
307
308       break;
309
310     default:
311       exit(EXIT_FAILURE);
312     }
313   }
314
315   if(optind + 1 < argc) {
316     start(argv[optind], argv[optind + 1]);
317   } else {
318     fprintf(stderr, "%s [OPTIONS] <dir1> <dir2>\n", argv[0]);
319     exit(EXIT_FAILURE);
320   }
321
322   exit(EXIT_SUCCESS);
323 }