commit 777502f1757da092a758d483e874f59e5ba66ab1 Author: Joachim Nilsson Date: Tue Oct 2 06:10:09 2012 +0200 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 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) +