From 183c73b02615acc33fc22e89b8cc7fdc5f22ee0e Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Sat, 12 May 2012 23:13:24 -0400 Subject: [PATCH] Teach build system to install relative symlinks if requested Add a configure option, --enable-relative-symlinks, which will use relative symlinks for the ELF shared library files. Addresses-Sourceforge-Bug: #3520767 Signed-off-by: "Theodore Ts'o" --- MCONFIG.in | 17 ++ configure | 34 +++- configure.in | 24 ++- lib/Makefile.elf-lib | 13 +- util/Makefile.in | 6 +- util/install-symlink.in | 89 ++++++++++ util/symlinks.c | 385 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 551 insertions(+), 17 deletions(-) create mode 100644 util/install-symlink.in create mode 100644 util/symlinks.c diff --git a/MCONFIG.in b/MCONFIG.in index bdb3580b..0c745220 100644 --- a/MCONFIG.in +++ b/MCONFIG.in @@ -135,6 +135,23 @@ DEP_SUBSTITUTE= $(top_builddir)/util/subst $(SUBST_CONF) $(top_builddir)/util/subst: cd $(top_builddir)/util ; $(MAKE) subst +# +# Script for installing symlinks (for shared libraries) +# +$(top_builddir)/util/install-symlink: $(top_srcdir)/util/install-symlink.in \ + $(top_builddir)/config.status + cd $(top_builddir); CONFIG_FILES=util/install-symlink ./config.status + chmod +x $(top_builddir)/util/install-symlink + +$(top_builddir)/util/symlinks: + cd $(top_builddir)/util ; $(MAKE) symlinks + +INSTALL_SYMLINK = /bin/sh $(top_builddir)/util/install-symlink \ + @SYMLINK_RELATIVE@ \ + --symlinks=$(top_builddir)/util/symlinks +DEP_INSTALL_SYMLINK = $(top_builddir)/util/install-symlink \ + $(top_builddir)/util/symlinks + # # Warning flags # diff --git a/configure b/configure index ac0426e8..ae599cd6 100755 --- a/configure +++ b/configure @@ -715,6 +715,7 @@ HTREE_CMT Q E LINK_BUILD_FLAGS +SYMLINK_RELATIVE LINK_INSTALL_FLAGS MAINTAINER_CMT CPP @@ -795,6 +796,7 @@ with_ldopts with_root_prefix enable_maintainer_mode enable_symlink_install +enable_symlink_relative_symlinks enable_symlink_build enable_verbose_makecmds enable_compression @@ -1449,6 +1451,7 @@ Optional Features: --enable-FEATURE[=ARG] include FEATURE [ARG=yes] --enable-maintainer-mode enable makefile rules useful for maintainers --enable-symlink-install use symlinks when installing instead of hard links + --enable-relative-symlinks use relative symlinks when installing --enable-symlink-build use symlinks while building instead of hard links --enable-verbose-makecmds enable verbose make command output --enable-compression enable EXPERIMENTAL compression support @@ -4376,8 +4379,29 @@ fi else LINK_INSTALL_FLAGS=-f -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Disabling symlinks for install" >&5 -$as_echo "Disabling symlinks for install" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Disabling symlinks for install by default" >&5 +$as_echo "Disabling symlinks for install by default" >&6; } + +fi + + +# Check whether --enable-symlink-relative-symlinks was given. +if test "${enable_symlink_relative_symlinks+set}" = set; then : + enableval=$enable_symlink_relative_symlinks; if test "$enableval" = "no" +then + SYMLINK_RELATIVE= + { $as_echo "$as_me:${as_lineno-$LINENO}: result: Disabling relative symlinks for install" >&5 +$as_echo "Disabling relative symlinks for install" >&6; } +else + SYMLINK_RELATIVE=--relative + { $as_echo "$as_me:${as_lineno-$LINENO}: result: Enabling symlinks for install" >&5 +$as_echo "Enabling symlinks for install" >&6; } +fi + +else + SYMLINK_RELATIVE= +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Disabling relative symlinks for install by default" >&5 +$as_echo "Disabling relative symlinks for install by default" >&6; } fi @@ -4397,8 +4421,8 @@ fi else LINK_BUILD_FLAGS= -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Disabling symlinks for build" >&5 -$as_echo "Disabling symlinks for build" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Disabling symlinks for build by default" >&5 +$as_echo "Disabling symlinks for build by default" >&6; } fi @@ -11293,7 +11317,7 @@ test -d include || mkdir include test -d include/linux || mkdir include/linux test -d include/asm || mkdir include/asm for i in MCONFIG Makefile e2fsprogs.spec \ - util/Makefile util/subst.conf util/gen-tarball \ + util/Makefile util/subst.conf util/gen-tarball util/install-symlink \ lib/et/Makefile lib/ss/Makefile lib/e2p/Makefile \ lib/ext2fs/Makefile lib/ext2fs/ext2_types.h \ lib/uuid/Makefile lib/uuid/uuid_types.h \ diff --git a/configure.in b/configure.in index e15a77a6..83a6a1d0 100644 --- a/configure.in +++ b/configure.in @@ -164,10 +164,28 @@ else fi , LINK_INSTALL_FLAGS=-f -AC_MSG_RESULT([Disabling symlinks for install]) +AC_MSG_RESULT([Disabling symlinks for install by default]) ) AC_SUBST(LINK_INSTALL_FLAGS) dnl +dnl handle --enable-relative-symlinks +dnl +AC_ARG_ENABLE([symlink-relative-symlinks], +[ --enable-relative-symlinks use relative symlinks when installing], +if test "$enableval" = "no" +then + SYMLINK_RELATIVE= + AC_MSG_RESULT([Disabling relative symlinks for install]) +else + SYMLINK_RELATIVE=--relative + AC_MSG_RESULT([Enabling symlinks for install]) +fi +, + SYMLINK_RELATIVE= +AC_MSG_RESULT([Disabling relative symlinks for install by default]) +) +AC_SUBST(SYMLINK_RELATIVE) +dnl dnl handle --enable-symlink-build dnl AC_ARG_ENABLE([symlink-build], @@ -182,7 +200,7 @@ else fi , LINK_BUILD_FLAGS= -AC_MSG_RESULT([Disabling symlinks for build]) +AC_MSG_RESULT([Disabling symlinks for build by default]) ) AC_SUBST(LINK_BUILD_FLAGS) dnl @@ -1230,7 +1248,7 @@ test -d include || mkdir include test -d include/linux || mkdir include/linux test -d include/asm || mkdir include/asm for i in MCONFIG Makefile e2fsprogs.spec \ - util/Makefile util/subst.conf util/gen-tarball \ + util/Makefile util/subst.conf util/gen-tarball util/install-symlink \ lib/et/Makefile lib/ss/Makefile lib/e2p/Makefile \ lib/ext2fs/Makefile lib/ext2fs/ext2_types.h \ lib/uuid/Makefile lib/uuid/uuid_types.h \ diff --git a/lib/Makefile.elf-lib b/lib/Makefile.elf-lib index ea600c7a..c24636cc 100644 --- a/lib/Makefile.elf-lib +++ b/lib/Makefile.elf-lib @@ -40,18 +40,15 @@ installdirs-elf-lib:: installdirs:: installdirs-elf-lib -install-shlibs install:: $(ELF_LIB) installdirs-elf-lib +install-shlibs install:: $(ELF_LIB) installdirs-elf-lib $(DEP_INSTALL_SYMLINK) $(E) " INSTALL-ELF-LIB $(ELF_INSTALL_DIR)/$(ELF_LIB)" $(Q) $(INSTALL_PROGRAM) $(ELF_LIB) $(DESTDIR)$(ELF_INSTALL_DIR)/$(ELF_LIB) $(E) " SYMLINK $(ELF_INSTALL_DIR)/$(ELF_SONAME)" - $(Q) $(LN_S) -f $(ELF_LIB) $(DESTDIR)$(ELF_INSTALL_DIR)/$(ELF_SONAME) + $(Q) $(INSTALL_SYMLINK) $(ELF_INSTALL_DIR)/$(ELF_LIB) \ + $(ELF_INSTALL_DIR)/$(ELF_SONAME) $(DESTDIR) $(E) " SYMLINK $(libdir)/$(ELF_IMAGE).so" - $(Q) if test "$(ELF_INSTALL_DIR)" = "$(libdir)"; then \ - $(LN_S) -f $(ELF_SONAME) $(DESTDIR)$(libdir)/$(ELF_IMAGE).so ; \ - else \ - $(LN_S) -f $(ELF_INSTALL_DIR)/$(ELF_SONAME) \ - $(DESTDIR)$(libdir)/$(ELF_IMAGE).so; \ - fi + $(Q) $(INSTALL_SYMLINK) $(ELF_INSTALL_DIR)/$(ELF_SONAME) \ + $(libdir)/$(ELF_IMAGE).so $(DESTDIR) $(E) " LDCONFIG" $(Q) -$(LDCONFIG) diff --git a/util/Makefile.in b/util/Makefile.in index 4ad769f6..adf0b461 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -17,7 +17,7 @@ SRCS = $(srcdir)/subst.c $(E) " CC $<" $(Q) $(BUILD_CC) -c $(BUILD_CFLAGS) $< -o $@ -PROGS= subst +PROGS= subst symlinks all:: $(PROGS) gen-tarball @@ -29,6 +29,10 @@ copy_sparse: copy_sparse.o $(E) " LD $@" $(Q) $(BUILD_CC) $(BUILD_LDFLAGS) -o copy_sparse copy_sparse.o +symlinks: symlinks.o + $(E) " LD $@" + $(Q) $(BUILD_CC) $(BUILD_LDFLAGS) -o symlinks symlinks.o + gen-tarball: $(srcdir)/gen-tarball.in $(top_builddir)/config.status $(E) " CONFIG.STATUS $@" $(Q) cd $(top_builddir); CONFIG_FILES=util/gen-tarball ./config.status diff --git a/util/install-symlink.in b/util/install-symlink.in new file mode 100644 index 00000000..24341b8b --- /dev/null +++ b/util/install-symlink.in @@ -0,0 +1,89 @@ +#!/bin/sh +# +# install-symlink source destination destdir +# + +SYMLINKS=symlinks +LN_S="@LN_S@" +RM="@RM@" +FORCE_RELATIVE=NO +FORCE_ABSOLUTE=NO + +while echo $1 | grep -q -- ^- ; +do + case $1 in + --relative) + FORCE_RELATIVE=YES + ;; + --absolute) + FORCE_ABSOLUTE=YES + ;; + --debian) + FORCE_ABSOLUTE=NO + FORCE_RELATIVE=NO + ;; + --symlinks=*) + SYMLINKS=$(echo $1 | sed -e 's/--symlinks=//') + ;; + *) + echo "Unknown option $1" + exit 1 + ;; + esac + shift; +done + + +FIX_SYMLINK="$SYMLINKS -c" + +SRC="$1" +DEST="$2" +DESTDIR="$3" + +if ! echo $SRC | grep -q ^/ ; then + echo $SRC: Source pathname must be absolute + exit 1 +fi + +if ! echo $DEST | grep -q ^/ ; then + echo $DEST: Destination pathname must be absolute + exit 1 +fi + +if ! test -e "$DESTDIR$SRC" ; then + echo $DESTDIR$SRC: file or directory does not exist + exit 1 +fi + +$RM -f "$DESTDIR$DEST" + +if test "$LN_S" != "ln -s" ; then + $LN_S "$DESTDIR$SRC" "$DESTDIR$DEST" + exit 0 +fi + +if test $(dirname "$SRC") = $(dirname "$DEST") ; then + $LN_S "$(basename "$SRC")" "$DESTDIR$DEST" + exit 0 +fi + +TOP_SRC=$(echo $SRC | awk -F/ '{print $2}') +TOP_DEST=$(echo $DEST | awk -F/ '{print $2}') + +if test $FORCE_RELATIVE = YES ; then + TOP_SRC=FORCE + TOP_DEST=FORCE +fi + +if test $FORCE_ABSOLUTE = YES ; then + TOP_SRC=FORCE + TOP_DEST=FORCE_ABSOLUTE +fi + +if test $TOP_SRC != $TOP_DEST ; then + $LN_S "$SRC" "$DESTDIR$DEST" +else + $LN_S "$DESTDIR$SRC" "$DESTDIR$DEST" + $FIX_SYMLINK "$DESTDIR$DEST" +fi + diff --git a/util/symlinks.c b/util/symlinks.c new file mode 100644 index 00000000..f3a632af --- /dev/null +++ b/util/symlinks.c @@ -0,0 +1,385 @@ +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE_SOURCE +#define _LARGEFILE64_SOURCE + +#include +#ifndef _POSIX_SOURCE +#define _POSIX_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef S_ISLNK +#define S_ISLNK(mode) (((mode) & (_S_IFMT)) == (_S_IFLNK)) +#endif + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +#define progver "%s: scan/change symbolic links - v1.3 - by Mark Lord\n\n" +static char *progname; +static int verbose = 0, fix_links = 0, recurse = 0, delete = 0, shorten = 0, + testing = 0, single_fs = 1; + +/* + * tidypath removes excess slashes and "." references from a path string + */ + +static int substr (char *s, char *old, char *new) +{ + char *tmp = NULL; + int oldlen = strlen(old), newlen = 0; + + if (NULL == strstr(s, old)) + return 0; + + if (new) + newlen = strlen(new); + + if (newlen > oldlen) { + if ((tmp = malloc(strlen(s))) == NULL) { + fprintf(stderr, "no memory\n"); + exit (1); + } + } + + while (NULL != (s = strstr(s, old))) { + char *p, *old_s = s; + + if (new) { + if (newlen > oldlen) + old_s = strcpy(tmp, s); + p = new; + while (*p) + *s++ = *p++; + } + p = old_s + oldlen; + while ((*s++ = *p++)); + } + if (tmp) + free(tmp); + return 1; +} + + +static int tidy_path (char *path) +{ + int tidied = 0; + char *s, *p; + + s = path + strlen(path) - 1; + if (s[0] != '/') { /* tmp trailing slash simplifies things */ + s[1] = '/'; + s[2] = '\0'; + } + while (substr(path, "/./", "/")) + tidied = 1; + while (substr(path, "//", "/")) + tidied = 1; + + while ((p = strstr(path,"/../")) != NULL) { + s = p+3; + for (p--; p != path; p--) if (*p == '/') break; + if (*p != '/') + break; + while ((*p++ = *s++)); + tidied = 1; + } + if (*path == '\0') + strcpy(path,"/"); + p = path + strlen(path) - 1; + if (p != path && *p == '/') + *p-- = '\0'; /* remove tmp trailing slash */ + while (p != path && *p == '/') { /* remove any others */ + *p-- = '\0'; + tidied = 1; + } + while (!strncmp(path,"./",2)) { + for (p = path, s = path+2; (*p++ = *s++);); + tidied = 1; + } + return tidied; +} + +static int shorten_path (char *path, char *abspath) +{ + static char dir[PATH_MAX]; + int shortened = 0; + char *p; + + /* get rid of unnecessary "../dir" sequences */ + while (abspath && strlen(abspath) > 1 && (p = strstr(path,"../"))) { + /* find innermost occurance of "../dir", and save "dir" */ + int slashes = 2; + char *a, *s, *d = dir; + while ((s = strstr(p+3, "../"))) { + ++slashes; + p = s; + } + s = p+3; + *d++ = '/'; + while (*s && *s != '/') + *d++ = *s++; + *d++ = '/'; + *d = '\0'; + if (!strcmp(dir,"//")) + break; + /* note: p still points at ../dir */ + if (*s != '/' || !*++s) + break; + a = abspath + strlen(abspath) - 1; + while (slashes-- > 0) { + if (a <= abspath) + goto ughh; + while (*--a != '/') { + if (a <= abspath) + goto ughh; + } + } + if (strncmp(dir, a, strlen(dir))) + break; + while ((*p++ = *s++)); /* delete the ../dir */ + shortened = 1; + } +ughh: + return shortened; +} + + +static void fix_symlink (char *path, dev_t my_dev) +{ + static char lpath[PATH_MAX], new[PATH_MAX], abspath[PATH_MAX]; + char *p, *np, *lp, *tail, *msg; + struct stat stbuf, lstbuf; + int c, fix_abs = 0, fix_messy = 0, fix_long = 0; + + if ((c = readlink(path, lpath, sizeof(lpath))) == -1) { + perror(path); + return; + } + lpath[c] = '\0'; /* readlink does not null terminate it */ + + /* construct the absolute address of the link */ + abspath[0] = '\0'; + if (lpath[0] != '/') { + strcat(abspath,path); + c = strlen(abspath); + if ((c > 0) && (abspath[c-1] == '/')) + abspath[c-1] = '\0'; /* cut trailing / */ + if ((p = strrchr(abspath,'/')) != NULL) + *p = '\0'; /* cut last component */ + strcat(abspath,"/"); + } + strcat(abspath,lpath); + (void) tidy_path(abspath); + + /* check for various things */ + if (stat(abspath, &stbuf) == -1) { + printf("dangling: %s -> %s\n", path, lpath); + if (delete) { + if (unlink (path)) { + perror(path); + } else + printf("deleted: %s -> %s\n", path, lpath); + } + return; + } + + if (single_fs) + lstat(abspath, &lstbuf); /* if the above didn't fail, then this shouldn't */ + + if (single_fs && lstbuf.st_dev != my_dev) { + msg = "other_fs:"; + } else if (lpath[0] == '/') { + msg = "absolute:"; + fix_abs = 1; + } else if (verbose) { + msg = "relative:"; + } else + msg = NULL; + fix_messy = tidy_path(strcpy(new,lpath)); + if (shorten) + fix_long = shorten_path(new, path); + if (!fix_abs) { + if (fix_messy) + msg = "messy: "; + else if (fix_long) + msg = "lengthy: "; + } + if (msg != NULL) + printf("%s %s -> %s\n", msg, path, lpath); + if (!(fix_links || testing) || !(fix_messy || fix_abs || fix_long)) + return; + + if (fix_abs) { + /* convert an absolute link to relative: */ + /* point tail at first part of lpath that differs from path */ + /* point p at first part of path that differs from lpath */ + (void) tidy_path(lpath); + tail = lp = lpath; + p = path; + while (*p && (*p == *lp)) { + if (*lp++ == '/') { + tail = lp; + while (*++p == '/'); + } + } + + /* now create new, with "../"s followed by tail */ + np = new; + while (*p) { + if (*p++ == '/') { + *np++ = '.'; + *np++ = '.'; + *np++ = '/'; + while (*p == '/') ++p; + } + } + strcpy (np, tail); + (void) tidy_path(new); + if (shorten) (void) shorten_path(new, path); + } + shorten_path(new,path); + if (!testing) { + if (unlink (path)) { + perror(path); + return; + } + if (symlink(new, path)) { + perror(path); + return; + } + } + printf("changed: %s -> %s\n", path, new); +} + +static void dirwalk (char *path, int pathlen, dev_t dev) +{ + char *name; + DIR *dfd; + static struct stat st; + static struct dirent *dp; + + if ((dfd = opendir(path)) == NULL) { + perror(path); + return; + } + + name = path + pathlen; + if (*(name-1) != '/') + *name++ = '/'; + + while ((dp = readdir(dfd)) != NULL ) { + strcpy(name, dp->d_name); + if (strcmp(name, ".") && strcmp(name,"..")) { + if (lstat(path, &st) == -1) { + perror(path); + } else if (st.st_dev == dev) { + if (S_ISLNK(st.st_mode)) { + fix_symlink (path, dev); + } else if (recurse && S_ISDIR(st.st_mode)) { + dirwalk(path, strlen(path), dev); + } + } + } + } + closedir(dfd); + path[pathlen] = '\0'; +} + +static void usage_error (void) +{ + fprintf(stderr, progver, progname); + fprintf(stderr, "Usage:\t%s [-cdorstv] LINK|DIR ...\n\n", progname); + fprintf(stderr, "Flags:" + "\t-c == change absolute/messy links to relative\n" + "\t-d == delete dangling links\n" + "\t-o == warn about links across file systems\n" + "\t-r == recurse into subdirs\n" + "\t-s == shorten lengthy links (displayed in output only when -c not specified)\n" + "\t-t == show what would be done by -c\n" + "\t-v == verbose (show all symlinks)\n\n"); + exit(1); +} + +int main(int argc, char **argv) +{ +#if defined (_GNU_SOURCE) && defined (__GLIBC__) + static char path[PATH_MAX+2]; + char* cwd = get_current_dir_name(); +#else + static char path[PATH_MAX+2], cwd[PATH_MAX+2]; +#endif + int dircount = 0; + char c, *p; + + if ((progname = (char *) strrchr(*argv, '/')) == NULL) + progname = *argv; + else + progname++; + +#if defined (_GNU_SOURCE) && defined (__GLIBC__) + if (NULL == cwd) { + fprintf(stderr,"get_current_dir_name() failed\n"); +#else + if (NULL == getcwd(cwd,PATH_MAX)) { + fprintf(stderr,"getcwd() failed\n"); +#endif + exit (1); + } +#if defined (_GNU_SOURCE) && defined (__GLIBC__) + cwd = realloc(cwd, strlen(cwd)+2); + if (cwd == NULL) { + fprintf(stderr, "realloc() failed\n"); + exit (1); + } +#endif + if (!*cwd || cwd[strlen(cwd)-1] != '/') + strcat(cwd,"/"); + + while (--argc) { + p = *++argv; + if (*p == '-') { + if (*++p == '\0') + usage_error(); + while ((c = *p++)) { + if (c == 'c') fix_links = 1; + else if (c == 'd') delete = 1; + else if (c == 'o') single_fs = 0; + else if (c == 'r') recurse = 1; + else if (c == 's') shorten = 1; + else if (c == 't') testing = 1; + else if (c == 'v') verbose = 1; + else usage_error(); + } + } else { + struct stat st; + if (*p == '/') + *path = '\0'; + else + strcpy(path,cwd); + tidy_path(strcat(path, p)); + if (lstat(path, &st) == -1) + perror(path); + else if (S_ISLNK(st.st_mode)) + fix_symlink(path, st.st_dev); + else + dirwalk(path, strlen(path), st.st_dev); + ++dircount; + } + } + if (dircount == 0) + usage_error(); + exit (0); +}