// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2019 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License // as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // BOINC 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . // antique_file deleter // removes files from the upload/download hierarchies that are // older than any WU (except "no_delete" WUs) // See usage() below for usage. // how long to wait until delete antiques, and how often to do it // #define ANTIQUE_USLEEP 50000 // worked good for E@H, can't be too bad #include "config.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_STRINGS_H #include #endif #include "boinc_db.h" #include "parse.h" #include "error_numbers.h" #include "util.h" #include "str_util.h" #include "str_replace.h" #include "filesys.h" #include "strings.h" #include "svn_version.h" #include "sched_config.h" #include "sched_util.h" #include "sched_msgs.h" #define LOCKFILE "antique_file_deleter.out" #define PIDFILE "antique_file_deleter.pid" int antique_usleep = ANTIQUE_USLEEP; bool antiques_deletion_dry_run = false; void usage(char *name) { fprintf(stderr, "Deletes files that have been uploaded after the result was purged from the DB.\n\n" " scan for and delete all files in the upload directories\n" " that are older than any WU in the database,\n" " and were created at least one month ago.\n" " This deletes files uploaded by hosts after the WU was deleted.\n\n" "Usage: %s [OPTION]...\n\n" "Options:\n" " -d N | --debug_level N set debug output level (1 to 4)\n" " --dry_run don't delete any files, just log what would be deleted\n" " --usleep N sleep this number of usecs after each examined file.\n" " Throttles I/O if there are many files. Defaults to %d.\n" " [ -h | --help ] shows this help text\n" " [ -v | --version ] shows version information\n", name, ANTIQUE_USLEEP ); } // Given a filename, find its full path in the upload directory hierarchy // Return ERR_OPENDIR if dir isn't there (possibly recoverable error), // ERR_NOT_FOUND if dir is there but not file // int get_file_path( const char *filename, char* upload_dir, int fanout, char* path ) { dir_hier_path(filename, upload_dir, fanout, path, true); if (boinc_file_exists(path)) { return 0; } char* p = strrchr(path, '/'); *p = 0; if (boinc_file_exists(path)) { return ERR_NOT_FOUND; } return ERR_OPENDIR; } // return ctime() w/o \n // static inline char* actime(time_t t) { char*p=ctime(&t); char*c=strchr(p,'\n'); if(c) *c='\0'; return p; } // returns: // 0 if all went ok; // 1 on a transient error affecting only a single file; // -1 on a serious error that should switch off antique file deletion // int delete_antiques_from_dir(char*dirpath, time_t mtime, uid_t uid) { DIR*dir; struct dirent*entry; struct stat fstat; int ret; char path[MAXPATHLEN]; // open directory errno = 0; dir = opendir(dirpath); if (!dir) { log_messages.printf(MSG_CRITICAL, "delete_antiques_from_dir(): " "Couldn't open dir '%s': %s (%d)\n", dirpath, strerror(errno), errno ); return -1; } // scan directory errno = 0; while ((entry = readdir(dir))) { // might be woken by a signal check_stop_daemons(); // construct absolute path of this entry safe_strcpy(path, dirpath); strcat(path, "/"); strcat(path, entry->d_name); // examine file log_messages.printf(MSG_DEBUG, "delete_antiques_from_dir(): examining file: '%s'\n", path ); // stat errno = 0; if (lstat(path, &fstat)) { log_messages.printf(MSG_NORMAL, "delete_antiques_from_dir(): couldn't stat '%s: %s (%d)'\n", path, strerror(errno), errno ); // regular file } else if ((fstat.st_mode & S_IFMT) != S_IFREG) { log_messages.printf(MSG_DEBUG,"not a regular plain file\n"); // skip hidden files such as ".nfs" } else if (entry->d_name[0] == '.') { log_messages.printf(MSG_DEBUG,"hidden file or directory\n"); // modification time } else if (fstat.st_mtime > mtime) { log_messages.printf(MSG_DEBUG,"too young: %s\n", actime(fstat.st_mtime)); // check owner (must be apache) } else if (fstat.st_uid != uid) { log_messages.printf(MSG_DEBUG,"wrong owner: id %d\n", fstat.st_uid); // skip if dry_run } else if (antiques_deletion_dry_run) { log_messages.printf(MSG_NORMAL, "Would delete '%s/%s' (%s)\n", dirpath, entry->d_name, actime(fstat.st_mtime)); // found no reason to skip, actually delete this file } else { log_messages.printf(MSG_NORMAL, "Deleting file '%s' (%s)\n", path, actime(fstat.st_mtime) ); errno = 0; if (unlink(path)) { log_messages.printf(MSG_CRITICAL, "delete_antiques_from_dir(): " "Couldn't unlink '%s: %s (%d)'\n", path, strerror(errno), errno ); closedir(dir); return 1; } } // throttle I/O if told to if (antique_usleep) { usleep(antique_usleep); } } // while (readdir()) // if the loop terminated because of an error if (errno) { log_messages.printf(MSG_CRITICAL, "delete_antiques_from_dir(): " "Couldn't read dir '%s': %s (%d)\n", dirpath, strerror(errno), errno ); closedir(dir); return 1; } // close dir errno = 0; ret = closedir(dir); if (ret) { log_messages.printf(MSG_CRITICAL, "delete_antiques_from_dir(): " "Couldn't close dir '%s': %s (%d)\n", dirpath, strerror(errno), errno ); } return ret; } // collect information and call delete_antiques_from_dir() // for every relevant directory // static int delete_antiques() { DB_WORKUNIT wu; time_t t = 0; int ret = 0; // t = min (create_time_of_oldest_wu, 31days_ago) t = time(0) - 32*86400; if (!wu.enumerate("where name not like '%nodelete%' order by id limit 1") && (t > wu.create_time)) { t = wu.create_time - 86400; } // find numerical userid of apache struct passwd *apache_info = getpwnam(config.httpd_user); if (!apache_info) { log_messages.printf(MSG_CRITICAL, "Couldn't find http_user '%s' in passwd\n", config.httpd_user ); return -1; } log_messages.printf(MSG_DEBUG, "delete_antiques(): " "Deleting files older than epoch %lu (%s) with userid %u\n", (unsigned long)t, actime(t), apache_info->pw_uid ); // if fanout is configured, scan every fanout directory, // else just the plain upload directory if (config.uldl_dir_fanout) { for(int d = 0; d < config.uldl_dir_fanout; d++) { char buf[270]; snprintf(buf, sizeof(buf), "%s/%x", config.upload_dir, d); log_messages.printf(MSG_DEBUG, "delete_antiques(): scanning upload fanout directory '%s'\n", buf ); ret = delete_antiques_from_dir(buf, t, apache_info->pw_uid); if (ret < 0) return ret; } } else { log_messages.printf(MSG_DEBUG, "delete_antiques(): scanning upload directory '%s'\n", config.upload_dir ); ret = delete_antiques_from_dir(config.upload_dir, t, apache_info->pw_uid); } return ret; } int main(int argc, char** argv) { int retval; int i; check_stop_daemons(); for (i=1; i