/* wydawca - automatic release submission daemon Copyright (C) 2007, 2008, 2009, 2010 Sergey Poznyakoff Wydawca 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. Wydawca 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 wydawca. If not, see . */ #include "wydawca.h" #include "save-cwd.h" /* Return true if ARG is NULL or is a sub-directory of DIR */ int sub_dir_p (const char *arg, const char *dir) { int dlen; if (!arg) return 0; dlen = strlen (dir); return strlen (arg) > dlen && memcmp (dir, arg, dlen) == 0 && arg[dlen] == '/'; } /* Concatenate BASE directory name, a single directory separator (/) and NAME. Return in PBASELEN the length of BASE, not counting eventual trailing slash. */ char * concat_dir (const char *base, const char *name, size_t *pbaselen) { size_t len = strlen (base); size_t size; char *dir; while (len > 0 && base[len-1] == '/') len--; size = len + 1 + strlen (name); dir = xmalloc (size + 1); memcpy (dir, base, len); dir[len++] = '/'; strcpy (dir + len, name); if (pbaselen) *pbaselen = len; return dir; } /* Create the directory DIR, eventually creating all intermediate directories starting from DIR + BASELEN. */ int create_hierarchy (char *dir, size_t baselen) { int rc; struct stat st; char *p; if (stat (dir, &st) == 0) { if (!S_ISDIR (st.st_mode)) { logmsg (LOG_ERR, _("component %s is not a directory"), dir); return 1; } return 0; } else if (errno != ENOENT) { logmsg (LOG_ERR, _("cannot stat file %s: %s"), dir, strerror (errno)); return 1; } p = strrchr (dir, '/'); if (p) { if (p - dir + 1 < baselen) { logmsg (LOG_ERR, _("base directory %s does not exist"), dir); return 1; } *p = 0; } rc = create_hierarchy (dir, baselen); if (rc == 0) { if (p) *p = '/'; if (mkdir (dir, MKDIR_PERMISSIONS)) { logmsg (LOG_ERR, _("cannot create directory %s: %s"), dir, strerror (errno)); rc = 1; } } return rc; } /* Create a directory BASE/NAME (with eventual intermediate directories in NAME). Do nothing if dry_run_mode is set. */ char * create_directory (const char *base, const char *name) { size_t baselen; char *dir = concat_dir (base, name, &baselen); if (!dry_run_mode) { if (create_hierarchy (dir, baselen)) { free (dir); dir = NULL; } } return dir; } /* Copy FILE to DST_FILE. */ int copy_file (const char *file, const char *dst_file) { int in_fd, out_fd; struct stat st; int rc; char *buf; size_t bufsize; size_t fsize; in_fd = open (file, O_RDONLY); if (in_fd == -1) { logmsg (LOG_ERR, _("cannot open source file %s for reading: %s"), file, strerror (errno)); return 1; } if (fstat (in_fd, &st)) { logmsg (LOG_ERR, _("cannot stat source file %s: %s"), file, strerror (errno)); close (in_fd); return 1; } out_fd = creat (dst_file, CREAT_PERMISSIONS); if (out_fd == -1) { logmsg (LOG_ERR, _("cannot create destination file %s: %s"), file, strerror (errno)); close (in_fd); return 1; } buf = NULL; fsize = st.st_size; for (bufsize = fsize; bufsize > 0 && (buf = malloc (bufsize)) == NULL; bufsize /= 2) ; if (bufsize == 0) xalloc_die (); rc = 0; while (fsize > 0) { size_t rest; size_t rdbytes; rest = fsize > bufsize ? bufsize : fsize; rdbytes = read (in_fd, buf, rest); if (rdbytes == -1) { logmsg (LOG_ERR, _("unexpected error reading %s: %s"), file, strerror (errno)); rc = 1; break; } rest = write (out_fd, buf, rdbytes); if (rest == -1) { logmsg (LOG_ERR, _("unexpected error writing to %s: %s"), dst_file, strerror (errno)); rc = 1; break; } else if (rest != rdbytes) { logmsg (LOG_ERR, _("short write on %s"), dst_file); rc = 1; } fsize -= rdbytes; } free (buf); close (in_fd); close (out_fd); if (rc) unlink (dst_file); return rc; } /* Move FILE to DST_FILE. If they reside on different devices, use copy_file + unlink. */ int do_move_file (const char *file, const char *dst_file) { int rc = 0; if (rename (file, dst_file)) { if (errno == EXDEV) { if (copy_file (file, dst_file)) { logmsg (LOG_CRIT, _("cannot copy %s to %s: %s"), file, dst_file, strerror (errno)); rc = 1; } else if (unlink (file)) { logmsg (LOG_ERR, _("cannot unlink %s: %s"), file, strerror (errno)); } } else { logmsg (LOG_CRIT, _("cannot move %s to %s: %s"), file, dst_file, strerror (errno)); rc = 1; } } return rc; } /* Append the FILE to the tar ARCHIVE. Do nothing if dry_run_mode is set. */ int tar_append_file (const char *archive, const char *file) { const char *argv[6]; if (debug_level) logmsg (LOG_DEBUG, _("tarring %s to %s"), file, archive); if (dry_run_mode) { UPDATE_STATS (STAT_ARCHIVES); return 0; } argv[0] = tar_command_name; argv[1] = "-f"; argv[2] = archive; argv[3] = "-r"; argv[4] = file; argv[5] = NULL; switch (wydawca_exec (6, argv, NULL)) { case exec_success: UPDATE_STATS (STAT_ARCHIVES); return 0; case exec_fail: case exec_error: logmsg (LOG_ERR, _("cannot archive %s"), file); break; } return 1; } /* Backup a file to a directory. Arguments: DST_FILE - File nam to back up. DST_DIR - Directory part of DST_FILE. FILE - File part of DST_FILE; can contain subdirs. ARCHIVE - Archive descriptor. RELDIR - Directory part of FILE Do nothing if dry_run_mode is set. */ int backup_file (const char *dst_file, const char *dst_dir, const char *file, const struct archive_descr *archive, const char *reldir) { int rc = 0; char *adir; char *file_name; if (archive->name[0] == '/') adir = create_directory (archive->name, reldir); else adir = create_directory (dst_dir, archive->name); if (!adir) return 1; file_name = concat_dir (adir, file, NULL); if (access (file_name, F_OK) == 0) { if (archive->backup_type == no_backups) { if (debug_level) logmsg (LOG_DEBUG, _("removing previous archive file `%s'"), file_name); if (!dry_run_mode && unlink (file_name)) { logmsg (LOG_ERR, _("cannot unlink previous archive file `%s': %s"), file_name, strerror (errno)); free (file_name); free (adir); return 1; } } else { char *archive_file_name = find_backup_file_name (file_name, archive->backup_type); if (debug_level) logmsg (LOG_DEBUG, _("backing up previous archive file `%s' to `%s'"), file_name, archive_file_name); if (!dry_run_mode) { rc = do_move_file (file_name, archive_file_name); if (rc) { logmsg (LOG_ERR, _("backing `%s' up as `%s' failed: %s"), file_name, archive_file_name, strerror (errno)); free (archive_file_name); free (file_name); free (adir); return 1; } } free (archive_file_name); } } if (debug_level) logmsg (LOG_DEBUG, _("archiving `%s' to `%s'"), dst_file, file_name); if (!dry_run_mode) { rc = do_move_file (dst_file, file_name); if (rc) logmsg (LOG_ERR, _("archiving `%s' as `%s' failed: %s"), dst_file, file_name, strerror (errno)); } free (file_name); free (adir); return rc; } /* Select the appropriate backup type and backup a file. See backup_file for the argument description. */ int do_archive_file (const char *dst_file, const char *dst_dir, const char *file, const struct archive_descr *archive, const char *reldir) { int rc = 0; switch (archive->type) { case archive_none: break; case archive_directory: if (backup_file (dst_file, dst_dir, file, archive, reldir)) return 1; UPDATE_STATS (STAT_ARCHIVES); break; case archive_tar: if (tar_append_file (archive->name, dst_file)) return 1; UPDATE_STATS (STAT_ARCHIVES); break; } if (!dry_run_mode && unlink (dst_file) && errno != ENOENT) { logmsg (LOG_ERR, _("canot unlink file `%s': %s"), dst_file, strerror (errno)); return 1; } return 0; } /* Move the part FILE_ID of the triplet TRP between the directories in TRP->SPOOL. TRP->RELATIVE_DIR gives relative directory (i.e. the directory part of the file name) for backup purposes. Do nothing if dry_run_mode is set. */ int dir_move_file (struct file_triplet *trp, enum file_type file_id) { char *dst_file; int rc = 0; const struct spool *spool = trp->spool; char *dst_dir = create_directory (spool->dest_dir, trp->relative_dir); if (!dst_dir) return 1; dst_file = concat_dir (dst_dir, trp->file[file_id].name, NULL); if (debug_level) logmsg (LOG_DEBUG, _("installing %s to %s"), trp->file[file_id].name, dst_dir); if (access (dst_file, F_OK) == 0) rc = do_archive_file (dst_file, dst_dir, trp->file[file_id].name, &spool->archive, trp->relative_dir); if (!dry_run_mode && rc == 0) rc = do_move_file (trp->file[file_id].name, dst_file); free (dst_file); free (dst_dir); if (rc == 0) UPDATE_STATS (STAT_UPLOADS); return rc; } /* Archive the file FILE_NAME, located in DPAIR->dest_dir, and remove the file. Get user IDs from the triplet TRP. Do nothing if dry_run_mode is set. */ int archive_single_file (struct file_triplet *trp, const char *file_name, int noentok) { char *dst_file; int rc = 0; const struct spool *spool = trp->spool; char *dst_dir = create_directory (spool->dest_dir, trp->relative_dir); if (!dst_dir) return 1; dst_file = safe_file_name (concat_dir (dst_dir, file_name, NULL)); if (!sub_dir_p (dst_file, spool->dest_dir)) { logmsg (LOG_ERR, _("file to be archived `%s' does not lie under `%s'"), dst_file, spool->dest_dir); free (dst_file); free (dst_dir); return 1; } if (access (dst_file, F_OK) == 0) { if (debug_level) logmsg (LOG_DEBUG, _("archiving file `%s'"), dst_file); rc = do_archive_file (dst_file, dst_dir, file_name, &spool->archive, trp->relative_dir); } else if (errno == ENOENT) { if (!noentok) logmsg (LOG_NOTICE, _("nothing to archive: file `%s' does not exist"), dst_file); } else { logmsg (LOG_ERR, _("canot access file `%s': %s"), dst_file, strerror (errno)); rc = 1; } free (dst_file); free (dst_dir); return rc; } static char * make_signame (const char *file_name) { size_t len; if (((len = strlen (file_name)) > SUF_SIG_LEN && memcmp (file_name + len - SUF_SIG_LEN, SUF_SIG, SUF_SIG_LEN))) { char *signame = xmalloc (len + SUF_SIG_LEN + 1); strcpy (signame, file_name); return strcat (signame, SUF_SIG); } return NULL; } /* Archive the file FILE_NAME, located in DPAIR->dest_dir, and remove the file. Get user IDs from the triplet TRP. Unless FILE_NAME ends in ".sig", do the same with FILE_NAME.sig, if such a file exists. Do nothing if dry_run_mode is set. */ int dir_archive_file (struct file_triplet *trp, const char *file_name) { int rc; char *signame; rc = archive_single_file (trp, file_name, 0); if (rc == 0 && archive_signatures && (signame = make_signame (file_name))) { rc = archive_single_file (trp, signame, 1); free (signame); } return rc; } /* Create a symbolic link from WANTED_SRC to WANTED_DST in the subdirectory TRP->relative_dir of SPOOL->dest_dir. Do nothing if dry_run_mode is set. */ int dir_symlink_file (struct file_triplet *trp, const char *wanted_src, const char *wanted_dst) { int rc = 0; struct saved_cwd cwd; const struct spool *spool = trp->spool; char *dst_dir = create_directory (spool->dest_dir, trp->relative_dir); char *src, *dst; if (!dst_dir) return 1; if (save_cwd (&cwd)) { logmsg (LOG_ERR, _("cannot save current directory: %s"), strerror (errno)); return 1; } src = safe_file_name_alloc (wanted_src); if (!src || src[0] == '/') { logmsg (LOG_ERR, _("symlink source `%s' does not lie under `%s'"), wanted_src, spool->dest_dir); free (src); return 1; } dst = safe_file_name_alloc (wanted_dst); if (!dst || dst[0] == '/') { logmsg (LOG_ERR, _("symlink destination `%s' does not lie under `%s'"), wanted_dst, spool->dest_dir); free (src); free (dst); return 1; } if (debug_level) logmsg (LOG_DEBUG, _("symlinking %s to %s in directory %s"), src, dst, dst_dir); if (!dry_run_mode) { char *p = strrchr (dst, '/'); if (p > dst) { char *dir; *p = 0; dir = create_directory (spool->dest_dir, dst); if (!dir) rc = 1; else free (dir); *p = '/'; } if (rc == 0) { if (chdir (dst_dir)) logmsg (LOG_ERR, _("cannot change to %s: %s"), dst_dir, strerror (errno)); else { struct stat st; if (lstat (dst, &st) == 0) { if (!S_ISLNK (st.st_mode)) { logmsg (LOG_ERR, _("file %s exists and is not a symbolic link"), dst); rc = 1; } else if (unlink (dst)) { logmsg (LOG_ERR, _("cannot unlink %s: %s"), dst, strerror (errno)); rc = 1; } } else if (errno != ENOENT) { logmsg (LOG_ERR, _("cannot stat file %s: %s"), dst, strerror (errno)); rc = 1; } if (rc == 0) { rc = symlink (src, dst); if (rc) logmsg (LOG_ERR, _("symlinking %s to %s in directory %s failed: %s"), src, dst, dst_dir, strerror (errno)); } } } } if (restore_cwd (&cwd)) { logmsg (LOG_EMERG, _("cannot restore current directory: %s"), strerror (errno)); exit (EX_SOFTWARE); } free (src); free (dst); free (dst_dir); if (rc == 0) UPDATE_STATS (STAT_SYMLINKS); return rc; } /* Auxiliary function for rmsymlink_file (see below) */ static int do_rmsymlink_file (const char *dst_file, int noentok) { struct stat st; if (debug_level) logmsg (LOG_DEBUG, _("removing symbolic link %s"), dst_file); if (stat (dst_file, &st)) { if (errno == ENOENT) { if (!noentok) logmsg (LOG_NOTICE, _("symlink `%s' does not exist"), dst_file); return 0; } if (!S_ISLNK (st.st_mode)) { logmsg (LOG_ERR, _("refusing to unlink %s: is not a symlink"), dst_file); return 1; } } if (!dry_run_mode && unlink (dst_file)) { logmsg (LOG_ERR, _("cannot unlink %s: %s"), dst_file, strerror (errno)); return 1; } UPDATE_STATS (STAT_RMSYMLINKS); return 0; } /* Remove the symbolic link TRP->spool->dest_dir/TRP->relative_dir/FILE_NAME Do nothing if dry_run_mode is set. */ int dir_rmsymlink_file (struct file_triplet *trp, const char *file_name) { char *dst_file; int rc = 0; char *signame; const struct spool *spool = trp->spool; char *dst_dir = create_directory (spool->dest_dir, trp->relative_dir); if (!dst_dir) return 1; dst_file = safe_file_name (concat_dir (dst_dir, file_name, NULL)); if (!sub_dir_p (dst_file, spool->dest_dir)) { logmsg (LOG_ERR, _("refusing to remove a symlink `%s' that is not " "located under `%s'"), dst_file, spool->dest_dir); free (dst_file); free (dst_dir); return 1; } rc = do_rmsymlink_file (dst_file, 0); if (rc == 0 && archive_signatures && (signame = make_signame (dst_file))) { rc = do_rmsymlink_file (signame, 1); free (signame); } free (dst_file); free (dst_dir); return rc; } int dir_test_url (mu_url_t url, grecs_locus_t *locus) { int rc; const char *dest_dir; rc = mu_url_sget_path (url, &dest_dir); if (rc) { grecs_error (locus, 0, _("cannot extract directory part from URL: %s"), mu_strerror (rc)); return rc; } if (test_dir (dest_dir, &rc)) { if (rc) grecs_error (locus, rc, _("cannot access %s"), dest_dir); else grecs_error (locus, 0, _("%s is not a directory"), dest_dir); return 1; } return 0; }