From 777502f1757da092a758d483e874f59e5ba66ab1 Mon Sep 17 00:00:00 2001 From: Joachim Nilsson Date: Tue, 2 Oct 2012 06:10:09 +0200 Subject: [PATCH] Fix I/O plugin watcher, load plugins earlier to enable a new early hook! Restructure start by calling the plugin loader earlier. This makes it possible to run hooks even earlier. This also adds a new hook: HOOK_ROOTFS_UP which runs as soon as the system root (/) is available. Another very notable change is the slow refactor of helpers.c into a finit helper library called libite.so (-lite). This is a light-weight Also, lots of misc. fixes all over the place. Signal handling still really needs to be looked into once more! Makefile: Add dependency handling for parallell build Add libite library ChangeLog: Update with new 1.4 release. There is a lot of fixes! libite/: Collection of utility functions and C library extensions for finit & its plugins reboot.c: New reboot tool. Simply sends SIGINT to PID 1 finit.h: Cleanup old LISTEN_INITCTL define, code is in an optional plugin now finit.c: Let mdev/udev run *after* cls() Add useful debug statement for /proc/cmdline Earlier plugin load => new early hook point Show OK/FAIL for total plugin load, not for each plugin => speedup helpers.h: Rename syslog macros to prevent common name clashes (DEBUG/ERROR) Add __FILE__ to _e(), _d() and _pe() macros helpers.c: In ifconfig() the addr and mask arguments are now optional The copyfile() has been moved to libite.so Temporarily remove const directive from some functions to build Use DFLSIG() macro in run_getty() to simplify code plugin.h: Add new HOOK_ROOTFS_UP hook plugin.c: Fix init_plugins() to actually register the plugin's I/O fd Skip verbose listing of all plugins in plugin_load_all() svc.c: svc_start(): Dito plugins/Makefile: Inherit CFLAGS and LDFLAGS, but make sure to filter out -rdynamic from LDFLAGS, it's used by the daemon, only plugins/initctl.c: New includes after helpers.h refactor Signed-off-by: Joachim Nilsson --- Makefile | 79 +++++++++++++ copyfile.c | 317 +++++++++++++++++++++++++++++++++++++++++++++++++++++ dir.c | 228 ++++++++++++++++++++++++++++++++++++++ fexist.c | 53 +++++++++ fisdir.c | 51 +++++++++ fmode.c | 57 ++++++++++ lite.h | 61 +++++++++++ rsync.c | 280 ++++++++++++++++++++++++++++++++++++++++++++++ rsync.mk | 22 ++++ 9 files changed, 1148 insertions(+) create mode 100644 Makefile create mode 100644 copyfile.c create mode 100644 dir.c create mode 100644 fexist.c create mode 100644 fisdir.c create mode 100644 fmode.c create mode 100644 lite.h create mode 100644 rsync.c create mode 100644 rsync.mk diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..daf0adb --- /dev/null +++ b/Makefile @@ -0,0 +1,79 @@ +# Finit libite -- Light-weight utility functions and C-library extensions +# +# Copyright (c) 2008-2012 Joachim Nilsson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +.EXPORT_ALL_VARIABLES: +.PHONY: all install clean + +include ../common.mk + +CFLAGS += -fPIC +HEADERS = lite.h +OBJS := copyfile.o dir.o fexist.o fisdir.o fmode.o rsync.o +DEPS := $(addprefix ., $(OBJS:.o=.d)) + +VER = 0 +LIBNAME = libite +TARGET = $(LIBNAME).so +VERLIB = $(TARGET).$(VER) +STATICLIB = $(LIBNAME).a + +CHECK_FLAGS = $(CFLAGS) $(CPPFLAGS) + +all: $(TARGET) + +$(OBJS): Makefile + +$(TARGET): $(OBJS) + @printf " LINK %s\n" $@ +# @$(AR) $(ARFLAGS) $(STATICLIB) $(OBJS) + @$(CC) $(LDFLAGS) -shared $(OBJS) -lrt -lcrypt -Wl,-soname,$@ -o $@ + @ln -sf $@ $(VERLIB) + +install-exec: all + @install -d $(DESTDIR)$(libdir) + @echo " INSTALL $(DESTDIR)$(libdir)/$(TARGET)" + @$(STRIPINST) $(TARGET) $(DESTDIR)$(libdir)/$(TARGET) + @ln -sf $(TARGET) $(DESTDIR)$(libdir)/$(VERLIB) + +install-dev: + @for file in $(HEADERS); do \ + printf " INSTALL $(DESTDIR)$(incdir)/$$file\n"; \ + $(INSTALL) -m 0644 $$file $(DESTDIR)$(incdir)/$$file; \ + done + +install: install-dev install-exec + +uninstall: + -@$(RM) $(DESTDIR)$(libdir)/$(VERLIB) + -@$(RM) $(DESTDIR)$(libdir)/$(TARGET) + +clean: uninstall + -@$(RM) $(OBJS) $(DEPS) $(TARGET) $(VERLIB) $(STATICLIB) + +distclean: clean + -@$(RM) $(JUNK) unittest *.o .*.d *.unittest + +ifneq ($(MAKECMDGOALS),clean) +ifneq ($(MAKECMDGOALS),distclean) +-include $(DEPS) +endif +endif diff --git a/copyfile.c b/copyfile.c new file mode 100644 index 0000000..d0867f8 --- /dev/null +++ b/copyfile.c @@ -0,0 +1,317 @@ +/* Fastinit (finit) copyfile() implementation. + * + * Copyright (c) 2008 Claudio Matsuoka + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include /* MAX(), isset(), setbit(), TRUE, FALSE, et consortes. :-) */ +#include +#include +#include + +#include "lite.h" + +static int __isdir_and_append_filename(char *src, char **dst); + +/** + * copyfile - Copy a file to another. + * @src: Source file. + * @dst: Target file. + * @len: Number of bytes to copy, zero (0) for entire file. + * + * This is a C implementation of the command line cp(1) utility. It is one + * of the classic missing links in the UNIX C library. This version is from + * the finit project, http://helllabs.org/finit/, which is a reimplementation + * of fastinit for the Asus EeePC. + * + * Only some small changes to save stack space have been introduced. + * + * Returns: + * The number of bytes copied, zero may be error (check errno!), but it + * may also indicate that @src was empty. If @src is a directory errno + * will be set to %EISDIR since copyfile() is not recursive. + */ +ssize_t copyfile(char *src, char *dst, int len) +{ + char *buffer; + int s, d, n, isdir = 0, saved_errno = 0; + ssize_t size = 0; + + errno = 0; /* Reset before leaving this function. */ +// fprintf (stderr, "%s() src: %s, dst: %s, len: %d\n", __func__, src, dst, len); + + buffer = malloc(BUFSIZ); + if (!buffer) + return 0; + + if (fisdir(src)) { + errno = EISDIR; /* Error: source is a directory */ + return 0; + } + + /* Check if target is a directory, then append the src base filename. */ + isdir = __isdir_and_append_filename(src, &dst); + + /* Len == 0 means copy entire file */ + if (len == 0) { + /* XXX: This feels a bit unsafe, but 2047 MiB may be sufficient for us. + * --Jocke 2008-08-11 */ + len = INT_MAX; + } + + if ((s = open(src, O_RDONLY)) >= 0) { + if ((d = + open(dst, O_WRONLY | O_CREAT | O_TRUNC, + fmode(src))) >= 0) { + do { + int clen = len > BUFSIZ ? BUFSIZ : len; + + if ((n = read(s, buffer, clen)) > 0) { + size += write(d, buffer, n); + } + len -= clen; + } + while (len > 0 && n == BUFSIZ); + + close(d); + } else { + saved_errno = errno; + } + + close(s); + } else { + saved_errno = errno; + } + + free(buffer); + if (isdir) + free(dst); + errno = saved_errno; + + return size; +} + +/** + * movefile - Move a file to another location + * @src: Source file. + * @dst: Target file, or location. + * + * This is a C implementation of the command line mv(1) utility. + * Usually the rename() API is sufficient, but not when moving across + * file system boundaries. + * + * The @src argument must include the full path to the source file, + * whereas the @dst argument may only be a directory, in which case the + * same file name from @src is used. + * + * Returns: + * POSIX OK(0), or non-zero with errno set. + */ +int movefile(char *src, char *dst) +{ + int isdir, result = 0; + + /* Check if target is a directory, then append the src base filename. */ + isdir = __isdir_and_append_filename(src, &dst); + + if (rename(src, dst)) { + if (errno == EXDEV) { + errno = 0; + copyfile(src, dst, 0); + if (errno) + result = 1; + else + result = remove(src); + } else { + result = 1; + } + } + + if (isdir) + free(dst); + + return result; +} + +/** + * copy_filep - Copy between FILE *fp. + * @src: Source FILE. + * @dst: Destination FILE. + * + * Function takes signals into account and will restart the syscalls as long + * as error is %EINTR. + * + * Returns: + * POSIX OK(0), or non-zero with errno set on error. + */ +int copy_filep(FILE *src, FILE *dst) +{ + int result = 0; + char *buf = (char *)malloc(BUFSIZ); + + if (!src || !dst) + return EINVAL; + + if (!buf) + return errno; + + while (1) { + if (!fgets(buf, BUFSIZ, src)) { + if (errno == EINTR) { + clearerr(src); + continue; + } + if (feof(src)) + break; + + result = errno; + goto exit; + } + while (EOF == fputs(buf, dst)) { + if (errno == EINTR) { + clearerr(dst); + continue; + } + + result = errno; + goto exit; + } + } + + exit: + free(buf); + return errno = result; +} + +/* Tests if dst is a directory, if so, reallocates dst and appends src filename returning 1 */ +static int __isdir_and_append_filename(char *src, char **dst) +{ + int isdir = 0; + + if (fisdir(*dst)) { + int slash = 0; + char *tmp, *ptr = strrchr(src, '/'); + + if (!ptr) + ptr = src; + else + ptr++; + +// fprintf (stderr, "Yes %s is a dir --> add %s\n", *dst, ptr); + tmp = malloc(strlen(*dst) + strlen(ptr) + 2); + if (!tmp) { + errno = EISDIR; + return 0; + } + + isdir = 1; /* Free dst before exit! */ + slash = fisslashdir(*dst); + + sprintf(tmp, "%s%s%s", *dst, slash ? "" : "/", ptr); + *dst = tmp; +// fprintf (stderr, "NEW DST:%s\n", *dst); + } + + return isdir; +} + +#ifdef UNITTEST +#include +/* XXX: Make a real set of unit tests ... */ +#if 0 +int main(int argc, char *argv[]) +{ + if (argc < 3) { + fprintf(stderr, + "Usage: %s \nCopies the file to the file.\n", + argv[0]); + return 1; + } + + printf("Copying file %s to %s\n", argv[1], argv[2]); + + return copyfile(argv[1], argv[2], 0); +} +#else +int main(void) +{ + int i = 0; + char *files[] = { + "/etc/passwd", "/tmp/tok", "/tmp/tok", + "/etc/passwd", "/tmp/", "/tmp/passwd", + "/etc/passwd", "/tmp", "/tmp/passwd", + NULL + }; + FILE *src, *dst; + + fprintf(stderr, "\n=>Start testing copy_filep()\n"); + while (files[i]) { + fprintf(stderr, "copy_filep(%s, %s)\t", files[i], + files[i + 1]); + src = fopen(files[i], "r"); + dst = fopen(files[i + 1], "w"); + if (copy_filep(src, dst)) + perror("Failed"); + + if (src) + fclose(src); + if (dst) + fclose(dst); + + if (fexist(files[i + 2])) + fprintf(stderr, "OK => %s\n", files[i + 2]); + + remove(files[i + 2]); + i += 3; + } + + printf("\n\n=>Start testing copyfile()\n"); + i = 0; + while (files[i]) { + fprintf(stderr, "copyfile(%s, %s)\t", files[i], files[i + 1]); + if (!copyfile(files[i], files[i + 1], 0)) + perror("Failed"); + + if (fexist(files[i + 2])) + fprintf(stderr, "OK => %s\n", files[i + 2]); + + remove(files[i + 2]); + i += 3; + } + + return 0; +} +#endif +#endif /* UNITTEST */ + +/** + * Local Variables: + * compile-command: "gcc -g -I../include -o unittest -DUNITTEST copyfile.c && ./unittest" + * version-control: t + * kept-new-versions: 2 + * c-file-style: "ellemtel" + * End: + */ diff --git a/dir.c b/dir.c new file mode 100644 index 0000000..3595d4d --- /dev/null +++ b/dir.c @@ -0,0 +1,228 @@ +/* Functions for operating on files in directories. + * + * Copyright (c) 2008-2012 Joachim Nilsson + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +static const char *matcher_type = NULL; +static int (*matcher_filter) (const char *file) = NULL; +static int matcher(const struct dirent *entry) +{ + char *pos = strrchr(entry->d_name, '.'); + + if (matcher_filter && !matcher_filter(entry->d_name)) + /* User matcher overrides the rest. */ + return 0; + + /* Skip current dir "." from list of files. */ + if ((1 == strlen(entry->d_name) && entry->d_name[0] == '.') || + (2 == strlen(entry->d_name) && !strcmp(entry->d_name, ".."))) + return 0; + + /* filetype == "" */ + if (matcher_type[0] == 0) + return 1; + + /* Entry has no "." */ + if (!pos) + return 0; + + return !strcmp(pos, matcher_type); +} + +/** + * dir - List all files of a certain type in the given directory. + * @dir: Base directory for dir operation. + * @type: File type suffix, e.g. ".cfg". + * @filter: Optional file name filter. + * @list: Pointer to an array of file names. + * @strip: Flag, if set dir() strips the file type. + * + * This function returns a @list of files, matching the @type suffix, + * in the given directory @dir. + * + * The @list argument is a pointer to where to store the dynamically + * allocated list of file names. This list should be free'd by first + * calling free() on each file name and then on the list itself. + * + * If @filter is not %NULL it will be called for each file found. If + * @filter returns non-zero the @file argument will be included in the + * resulting @list. If @filter returns zero for given @file it will + * be discarded. + * + * If the @strip flag is set the resulting @list of files has their + * file type stripped, including the dot. So a match "config0.cfg" + * would be returned as "config0". + * + * Returns: + * Number of files in @list, zero if no matching files of @type. + */ +int dir(const char *dir, const char *type, int (*filter) (const char *file), char ***list, int strip) +{ + int i, n, num = 0; + char **files; + struct dirent **namelist; + + assert(list); + + if (!dir) + /* Assuming current directory */ + dir = "."; + if (!type) + /* Assuming all files. */ + type = ""; + + matcher_type = type; + matcher_filter = filter; + n = scandir(dir, &namelist, matcher, alphasort); + if (n < 0) { + perror("scandir"); + } else { + files = (char **)malloc(n * sizeof(char *)); + for (i = 0; i < n; i++) { + if (files) { + char *name = namelist[i]->d_name; + char *type = strrchr(name, '.'); + + if (type && strip) + *type = 0; + + files[i] = strdup(name); + num++; + } + free(namelist[i]); + } + free(namelist); + } + + if (num) + *list = files; + + return num; +} + +#ifdef UNITTEST +#include +#include +#include +#include "otn/c.h" + +#define DIR_TYPE_IMAGE ".img" +#define DIR_TYPE_SYSLOG "" +#define DIR_TYPE_CONFIG ".cfg" +#define STARTUP_CONFIG "startup-config.cfg" + +int simulate_files(int creat) +{ + int i; + char *files[] = + { "config0.cfg", "config1.cfg", "config2.cfg", "config3.cfg", + "rr109.img", "rx100.img", "rx107.img", "rm957.img", "messages" + }; + + for (i = 0; i < ARRAY_ELEMENTS(files); i++) { + if (creat) + touch(files[i]); + else + remove(files[i]); + } + + if (creat) + symlink("config2.cfg", STARTUP_CONFIG); + else + remove(STARTUP_CONFIG); + + return 0; +} + +static int cfg_dir_filter(const char *file) +{ + /* Skip the STARTUP_CONFIG file, it is a symbolic link to the + * current startup configuration. */ + return ! !strcmp(file, STARTUP_CONFIG); +} + +int main(int argc, char *argv[]) +{ + int i, num; + char *type = DIR_TYPE_CONFIG; + char **files; + int once = 1; + + int is_startup_config(const char *entry) { + static char file[80]; + + if (once) { + int len = readlink(STARTUP_CONFIG, file, sizeof(file)); + + if (len == -1) + return 0; + + file[len] = 0; + once = 0; /* Only once per call to dir() */ + } + //printf ("Comparing link %s with entry %s\n", file, entry); + return !strcmp(file, entry); + } + + simulate_files(1); + + if (argc >= 2) { + if (!strcasecmp("CONFIG", argv[1])) { + type = DIR_TYPE_CONFIG; + system("ls -l *" DIR_TYPE_CONFIG); + } + if (!strcasecmp("IMAGE", argv[1])) { + type = DIR_TYPE_IMAGE; + system("ls -l *" DIR_TYPE_IMAGE); + } + if (!strcasecmp("SYSLOG", argv[1])) { + type = DIR_TYPE_SYSLOG; + system("ls -l *"); + } + } + + num = dir(NULL, type, cfg_dir_filter, &files, 0); + if (num) { + for (i = 0; i < num; i++) { + printf("%s", files[i]); + if (is_startup_config(files[i])) + printf(" --> startup-config"); + printf("\n"); + + free(files[i]); + } + free(files); + } + + simulate_files(0); + + return 0; +} +#endif /* UNITTEST */ + +/** + * Local Variables: + * compile-command: "gcc -g -DUNITTEST -o unittest dir.c" + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/fexist.c b/fexist.c new file mode 100644 index 0000000..88e6af7 --- /dev/null +++ b/fexist.c @@ -0,0 +1,53 @@ +/* Check if file exists + * + * Copyright (c) 2008 Claudio Matsuoka + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +/** + * fexist - Check if a file exists in the file system. + * @file: File to look for, with full path. + * + * Returns: + * %TRUE(1) if the file exists, otherwise %FALSE(0). + */ +int fexist(char *file) +{ + if (!file) { + errno = EINVAL; + return 0; /* Doesn't exist ... */ + } + + if (-1 == access(file, F_OK)) + return 0; + + return 1; +} + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/fisdir.c b/fisdir.c new file mode 100644 index 0000000..2eeb98b --- /dev/null +++ b/fisdir.c @@ -0,0 +1,51 @@ +/* Check if directory exists + * + * Copyright (c) 2008 Claudio Matsuoka + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +/** + * fexist - Check if a file exists and is a directory. + * @file: File to look for, with full path. + * + * Returns: + * %TRUE(1) if the file exists and is a directory, otherwise %FALSE(0). + */ +int fisdir(char *file) +{ + struct stat sb; + + if (!stat(file, &sb) && S_ISDIR(sb.st_mode)) + return 1; + + return 0; +} + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/fmode.c b/fmode.c new file mode 100644 index 0000000..7b3e68f --- /dev/null +++ b/fmode.c @@ -0,0 +1,57 @@ +/* File mode bit operations + * + * Copyright (c) 2008 Claudio Matsuoka + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include +#include + +/** + * fmode - Return a file's mode bits. + * @file: File to look for, with full path. + * + * Returns: + * The mode bits or zero when @file does not exist and errno set to %ENOENT. + */ +mode_t fmode(char *file) +{ + struct stat sb; + + if (!file) { + errno = EINVAL; + return 0; /* Doesn't exist ... */ + } + + if (!stat(file, &sb)) + return sb.st_mode; + + return 0; +} + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/lite.h b/lite.h new file mode 100644 index 0000000..6842e11 --- /dev/null +++ b/lite.h @@ -0,0 +1,61 @@ +/* Collection of utility funcs and C library extensions for finit & its plugins + * + * Copyright (c) 2008-2010 Claudio Matsuoka + * Copyright (c) 2008-2012 Joachim Nilsson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef FINIT_LITE_H_ +#define FINIT_LITE_H_ + +#include +#include +#include +#include +#include + +int fexist (char *file); +int fisdir (char *file); +mode_t fmode (char *file); + +ssize_t copyfile (char *src, char *dst, int len); +int movefile (char *src, char *dst); +int copy_filep (FILE *src, FILE *dst); + +int dir (const char *dir, const char *type, int (*filter) (const char *file), char ***list, int strip); +int rsync (char *src, char *dst, int delete, int (*filter) (const char *file)); + +static inline int fisslashdir(char *dir) +{ + if (!dir) return 0; + if (strlen (dir) > 0) return dir[strlen (dir) - 1] == '/'; + return 0; +} + +#endif /* FINIT_LITE_H_ */ + +/** + * Local Variables: + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ + diff --git a/rsync.c b/rsync.c new file mode 100644 index 0000000..7a660e3 --- /dev/null +++ b/rsync.c @@ -0,0 +1,280 @@ +/* Micro "rsync" implementation. + * + * Copyright (c) 2011, 2012 Joachim Nilsson + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include /* NULL, free() */ +#include /* strlen() */ +#include /* rindex() */ +#include +#include /* MAX(), isset(), setbit(), TRUE, FALSE, et consortes. :-) */ +#include +#include + +#include "lite.h" +#include "../helpers.h" + +static int makedir(char *buf, size_t buf_len, char *dir, char *name, mode_t mode); +static int prune(char *dst, char **new_files, int new_num); + + +/** + * rsync - Synchronize contents and optionally remove non-existing backups + * @src: Source directory + * @dst: Destination directory + * @delete: Prune files from @dst that no longer exist in @src. + * @filter: Optional filtering function for source directory. + * + * This is a miniature implementation of the famous rsync for local use only. + * In fact, it is not even a true rsync since it copies all files from @src + * to @dst. The @delete option is useful for creating backups, when set all + * files removed from src since last backup are pruned from the destination + * (backup) directory. + * + * The filter callback, @filter, if provided, is used to determine what files to + * include from the source directory when backing up. If a file is to be skipped + * the callback should simply return zero. + * + * Returns: + * POSIX OK(0), or non-zero with @errno set on error. + */ +int rsync(char *src, char *dst, int delete, int (*filter) (const char *file)) +{ + char source[256]; + char dest[256]; + int i = 0, num = 0, result = 0; + char **files; /* Array of file names. */ + + void copy(char *src, char *dst) + { + errno = 0; + copyfile(src, dst, 0); + if (errno) result++; + } + + if (!fisdir(dst)) + mkdir(dst, 0755); + + if (!fisdir(src)) { + if (!fexist(src)) + return 1; + + copy(src, dst); + return errno; + } + + /* Copy dir as well? */ + if (!fisslashdir(src)) { + char *ptr = rindex(src, '/'); + + if (!ptr) + ptr = src; + else + ptr++; + + if (makedir(dest, sizeof(dest), dst, ptr, fmode(src))) + return 1; + dst = dest; + } + + num = dir(src, "", filter, &files, 0); + for (i = 0; i < num; i++) { + /* Recursively copy sub-directries */ + snprintf(source, sizeof(source), "%s%s%s", src, fisslashdir(src) ? "" : "/", files[i]); + if (fisdir(source)) { + char dst2[256]; + + strcat(source, "/"); + if (makedir (dst2, sizeof(dst2), dst, files[i], fmode(source))) { + result++; + continue; + } + + rsync(source, dst2, delete, filter); + continue; /* Next file/dir in @src to copy... */ + } + copy(source, dst); + } + + /* We ignore any errors from the pruning, that phase albeit useful is only + * cosmetic. --Jocke 2011-03-24 */ + if (delete) + prune(dst, files, num); + + if (num) { + for (i = 0; i < num; i++) + free(files[i]); + free(files); + } + + return result; +} + +/* Creates dir/name @mode ... skipping / if dir already ends so. */ +static int makedir(char *buf, size_t buf_len, char *dir, char *name, mode_t mode) +{ + snprintf(buf, buf_len, "%s%s%s/", dir, fisslashdir(dir) ? "" : "/", name); + if (mkdir(buf, mode)) + return 1; + + return 0; +} + +/* Prune old files, no longer existing on source, from destination directory. */ +static int prune(char *dst, char **new_files, int new_num) +{ + int i, num, result = 0; + char **files; + + int find(char *file) { + int n; + + for (n = 0; n < new_num; n++) + if (!strncmp (new_files[n], file, MAX(strlen(new_files[n]), strlen(file)))) + return 1; + + return 0; + } + + num = dir(dst, "", NULL, &files, 0); + if (num) { + for (i = 0; i < num; i++) { + if (!find(files[i])) { + char *name; + size_t len = strlen(files[i]) + 2 + strlen(dst); + + name = malloc(len); + if (name) { + snprintf(name, len, "%s%s%s", dst, fisslashdir(dst) ? "" : "/", files[i]); + if (remove(name)) + result++; + free(name); + } + } + free(files[i]); + } + free(files); + } + + return result; +} + +#ifdef UNITTEST +#define BASE "/tmp/.unittest/" +#define SRC BASE "src/" +#define DST BASE "dst/" + +char *files[] = { + SRC "sub1/1.tst", + SRC "sub1/2.tst", + SRC "sub1/3.tst", + SRC "sub2/4.tst", + SRC "sub2/5.tst", + SRC "sub2/6.tst", + SRC "sub3/7.tst", + SRC "sub3/8.tst", + SRC "sub3/9.tst", + NULL +}; + +void cleanup_test(void) +{ + system("rm -rf " BASE); +} + +void setup_test(void) +{ + int i; + char cmd[256]; + mode_t dir_modes[] = { 755, 700 }; + mode_t file_modes[] = { 644, 600 }; + + cleanup_test(); + + mkdir(BASE, 0755); + mkdir(SRC, 0755); + mkdir(DST, 0755); + + for (i = 0; files[i]; i++) { + snprintf(cmd, sizeof(cmd), "mkdir -m %d -p `dirname %s`", + dir_modes[i % 2], files[i]); + system(cmd); + + snprintf(cmd, sizeof(cmd), "touch %s; chmod %d %s", files[i], + file_modes[i % 2], files[i]); + system(cmd); + } +} + +int run_test() +{ +#if 0 + setup_test(); + puts("Before:"); + system("tree -np " BASE); + + rsync(SRC, DST, 0, NULL); + puts("After:"); + system("tree -np " BASE); + cleanup_test(); +#endif + + setup_test(); + rsync(BASE "src", DST, 0, NULL); + puts("Only partial rsync of src <-- No slash!"); + system("tree -np " BASE); +#if 0 + cleanup_test(); + setup_test(); + rsync(BASE "src/sub1", BASE "dst", 0, NULL); + puts("Only partial rsync of src/sub1 <-- No slashes!!"); + system("tree -np " BASE); + + cleanup_test(); + setup_test(); + rsync(BASE "src/sub1/", DST, 0, NULL); + puts("Only partial rsync of src/sub1/"); + system("tree -np " BASE); + + cleanup_test(); + setup_test(); + rsync(BASE "src/sub1", DST, 0, NULL); + puts("Only partial rsync of src/sub1 <-- No slash!"); + system("tree -np " BASE); + + rsync("/etc", "/var/tmp", 0, NULL); + puts("Real life test:"); + system("tree -np /var/tmp"); +#endif + return 0; +} + +int main(int argc, char *argv[]) +{ + atexit(cleanup_test); + + return run_test(); +} +#endif /* UNITTEST */ + +/** + * Local Variables: + * compile-command: "make -f rsync.mk" + * version-control: t + * indent-tabs-mode: t + * c-file-style: "linux" + * End: + */ diff --git a/rsync.mk b/rsync.mk new file mode 100644 index 0000000..8556c78 --- /dev/null +++ b/rsync.mk @@ -0,0 +1,22 @@ +# Unittest makefile for rsync.c +.PHONY: clean + +SRC = rsync.c +OBJS = dir.o copyfile.o fexist.o fisdir.o fmode.o +UNIT = rsync.unittest +#CPPFLAGS = -DDEBUG -I../../.. -I../../include + +all: build run + +clean: + @rm -f $(OBJS) $(UNIT) + +# This rule also forces a cleanup of built objects to not leave any object files +# lingering in the tree to interfere with cross builds. +build: clean $(OBJS) + $(CC) $(CFLAGS) $(CPPFLAGS) -o $(UNIT) -DUNITTEST $(SRC) $(OBJS) + @rm -f $(OBJS) + +run: + @./$(UNIT) +