1 /* cleantmp 1.6 - clean out a tmp dir. Author: Kees J. Bot 2 * 11 Apr 1991 3 */ 4 #define nil 0 5 #include <sys/types.h> 6 #include <stdio.h> 7 #include <stddef.h> 8 #include <stdlib.h> 9 #include <unistd.h> 10 #include <sys/stat.h> 11 #include <string.h> 12 #include <time.h> 13 #include <dirent.h> 14 #include <errno.h> 15 16 #define arraysize(a) (sizeof(a) / sizeof((a)[0])) 17 #define arraylimit(a) ((a) + arraysize(a)) 18 19 #ifndef S_ISLNK 20 /* There were no symlinks in medieval times. */ 21 #define lstat stat 22 #endif 23 24 #ifndef DEBUG 25 #ifndef NDEBUG 26 #define NDEBUG 27 #endif 28 #endif 29 #include <assert.h> 30 31 #define SEC_DAY (24 * 3600L) /* A full day in seconds */ 32 #define DOTDAYS 14 /* Don't remove tmp/.* in at least 14 days. */ 33 34 void report(const char *label) 35 { 36 fprintf(stderr, "cleantmp: %s: %s\n", label, strerror(errno)); 37 } 38 39 void fatal(const char *label) 40 { 41 report(label); 42 exit(1); 43 } 44 45 void *alloc(size_t s) 46 { 47 void *mem; 48 49 if ((mem= (void *) malloc(s)) == nil) fatal(""); 50 return mem; 51 } 52 53 int force= 0; /* Force remove all. */ 54 int debug= 0; /* Debug level. */ 55 56 void days2time(unsigned long days, time_t *retired, time_t *dotretired) 57 { 58 struct tm *tm; 59 time_t t; 60 61 time(&t); 62 63 tm= localtime(&t); 64 tm->tm_hour= 0; 65 tm->tm_min= 0; 66 tm->tm_sec= 0; /* Step back to midnight of this day. */ 67 t= mktime(tm); 68 69 if (t < (days - 1) * SEC_DAY) { 70 *retired= *dotretired= 0; 71 } else { 72 *retired= t - (days - 1) * SEC_DAY; 73 *dotretired= t - (DOTDAYS - 1) * SEC_DAY; 74 if (*dotretired > *retired) *dotretired= *retired; 75 } 76 if (debug >= 2) fprintf(stderr, "Retired: %s", ctime(retired)); 77 if (debug >= 2) fprintf(stderr, "Dotretired: %s", ctime(dotretired)); 78 } 79 80 /* Path name construction, addpath adds a component, delpath removes it. 81 * The string 'path' is used throughout the program as the file under 82 * examination. 83 */ 84 85 char *path; /* Path name constructed in path[]. */ 86 int plen= 0, pidx= 0; /* Lenght/index for path[]. */ 87 88 void addpath(int *didx, const char *name) 89 /* Add a component to path. (name may also be a full path at the first call) 90 * The index where the current path ends is stored in *pdi. 91 */ 92 { 93 if (plen == 0) path= (char *) alloc((plen= 32) * sizeof(path[0])); 94 95 *didx= pidx; /* Record point to go back to for delpath. */ 96 97 if (pidx > 0 && path[pidx-1] != '/') path[pidx++]= '/'; 98 99 do { 100 if (*name != '/' || pidx == 0 || path[pidx-1] != '/') { 101 if (pidx == plen && 102 (path= (char *) realloc((void *) path, 103 (plen*= 2) * sizeof(path[0]))) == nil 104 ) fatal(""); 105 path[pidx++]= *name; 106 } 107 } while (*name++ != 0); 108 109 --pidx; /* Put pidx back at the null. The path[pidx++]= '/' 110 * statement will overwrite it at the next call. 111 */ 112 assert(pidx < plen); 113 } 114 115 void delpath(int didx) 116 { 117 assert(0 <= didx); 118 assert(didx <= pidx); 119 path[pidx= didx]= 0; 120 } 121 122 struct file { 123 struct file *next; 124 char *name; 125 }; 126 127 struct file *listdir(void) 128 { 129 DIR *dp; 130 struct dirent *entry; 131 struct file *first, **last= &first; 132 133 if ((dp= opendir(path)) == nil) { 134 report(path); 135 return nil; 136 } 137 138 while ((entry= readdir(dp)) != nil) { 139 struct file *new; 140 141 if (strcmp(entry->d_name, ".") == 0 142 || strcmp(entry->d_name, "..") == 0) continue; 143 144 new= (struct file *) alloc(sizeof(*new)); 145 new->name= (char *) alloc((size_t) strlen(entry->d_name) + 1); 146 strcpy(new->name, entry->d_name); 147 148 *last= new; 149 last= &new->next; 150 } 151 closedir(dp); 152 *last= nil; 153 154 return first; 155 } 156 157 struct file *shorten(struct file *list) 158 { 159 struct file *junk; 160 161 assert(list != nil); 162 163 junk= list; 164 list= list->next; 165 166 free((void *) junk->name); 167 free((void *) junk); 168 169 return list; 170 } 171 172 /* Hash list of files to ignore. */ 173 struct file *ignore_list[1024]; 174 size_t n_ignored= 0; 175 176 unsigned ihash(const char *name) 177 /* A simple hashing function on a file name. */ 178 { 179 unsigned h= 0; 180 181 while (*name != 0) h= (h * 0x1111) + *name++; 182 183 return h & (arraysize(ignore_list) - 1); 184 } 185 186 void do_ignore(int add, const char *name) 187 /* Add or remove a file to/from the list of files to ignore. */ 188 { 189 struct file **ipp, *ip; 190 191 ipp= &ignore_list[ihash(name)]; 192 while ((ip= *ipp) != nil) { 193 if (strcmp(name, ip->name) <= 0) break; 194 ipp= &ip->next; 195 } 196 197 if (add) { 198 ip= alloc(sizeof(*ip)); 199 ip->name= alloc((strlen(name) + 1) * sizeof(ip->name[0])); 200 strcpy(ip->name, name); 201 ip->next= *ipp; 202 *ipp= ip; 203 n_ignored++; 204 } else { 205 assert(ip != nil); 206 *ipp= ip->next; 207 free(ip->name); 208 free(ip); 209 n_ignored--; 210 } 211 } 212 213 int is_ignored(const char *name) 214 /* Is a file in the list of ignored files? */ 215 { 216 struct file *ip; 217 int r; 218 219 ip= ignore_list[ihash(name)]; 220 while (ip != nil) { 221 if ((r = strcmp(name, ip->name)) <= 0) return (r == 0); 222 ip= ip->next; 223 } 224 return 0; 225 } 226 227 #define is_ignored(name) (n_ignored > 0 && (is_ignored)(name)) 228 229 time_t retired, dotretired; 230 231 enum level { TOP, DOWN }; 232 233 void cleandir(enum level level, time_t retired) 234 { 235 struct file *list; 236 struct stat st; 237 time_t ret; 238 239 if (debug >= 2) fprintf(stderr, "Cleaning %s\n", path); 240 241 list= listdir(); 242 243 while (list != nil) { 244 int didx; 245 246 ret= (level == TOP && list->name[0] == '.') ? 247 dotretired : retired; 248 /* don't rm tmp/.* too soon. */ 249 250 addpath(&didx, list->name); 251 252 if (is_ignored(path)) { 253 if (debug >= 1) fprintf(stderr, "ignoring %s\n", path); 254 do_ignore(0, path); 255 } else 256 if (is_ignored(list->name)) { 257 if (debug >= 1) fprintf(stderr, "ignoring %s\n", path); 258 } else 259 if (lstat(path, &st) < 0) { 260 report(path); 261 } else 262 if (S_ISDIR(st.st_mode)) { 263 cleandir(DOWN, ret); 264 if (force || st.st_mtime < ret) { 265 if (debug < 3 && rmdir(path) < 0) { 266 if (errno != ENOTEMPTY 267 && errno != EEXIST) { 268 report(path); 269 } 270 } else { 271 if (debug >= 1) { 272 fprintf(stderr, 273 "rmdir %s\n", path); 274 } 275 } 276 } 277 } else { 278 if (force || (st.st_atime < ret 279 && st.st_mtime < ret 280 && st.st_ctime < ret) 281 ) { 282 if (debug < 3 && unlink(path) < 0) { 283 if (errno != ENOENT) { 284 report(path); 285 } 286 } else { 287 if (debug >= 1) { 288 fprintf(stderr, 289 "rm %s\n", path); 290 } 291 } 292 } 293 } 294 delpath(didx); 295 list= shorten(list); 296 } 297 } 298 299 void usage(void) 300 { 301 fprintf(stderr, 302 "Usage: cleantmp [-d[level]] [-i file ] ... -days|-f directory ...\n"); 303 exit(1); 304 } 305 306 int main(int argc, char **argv) 307 { 308 int i; 309 unsigned long days; 310 311 i= 1; 312 while (i < argc && argv[i][0] == '-') { 313 char *opt= argv[i++] + 1; 314 315 if (opt[0] == '-' && opt[1] == 0) break; 316 317 if (opt[0] == 'd') { 318 debug= 1; 319 if (opt[1] != 0) debug= atoi(opt + 1); 320 } else 321 if (opt[0] == 'i') { 322 if (*++opt == 0) { 323 if (i == argc) usage(); 324 opt= argv[i++]; 325 } 326 do_ignore(1, opt); 327 } else 328 if (opt[0] == 'f' && opt[1] == 0) { 329 force= 1; 330 days= 1; 331 } else { 332 char *end; 333 days= strtoul(opt, &end, 10); 334 if (*opt == 0 || *end != 0 335 || days == 0 336 || ((time_t) (days * SEC_DAY)) / SEC_DAY != days 337 ) { 338 fprintf(stderr, 339 "cleantmp: %s is not a valid number of days\n", 340 opt); 341 exit(1); 342 } 343 } 344 } 345 if (days == 0) usage(); 346 347 days2time(days, &retired, &dotretired); 348 349 while (i < argc) { 350 int didx; 351 352 if (argv[i][0] == 0) { 353 fprintf(stderr, "cleantmp: empty pathname!\n"); 354 exit(1); 355 } 356 addpath(&didx, argv[i]); 357 cleandir(TOP, retired); 358 delpath(didx); 359 assert(path[0] == 0); 360 i++; 361 } 362 exit(0); 363 } 364