view lib/relpath.c @ 16909:6b45aca5c4b2 akim/relpath

relpath: do not depend on xalloc.h. Suggested by Bruno Haible. * lib/relpath.c (convert_abs_rel): Embrace malloc failures. Simplify some conditionals. * lib/relpath.h: Adjust the documentation. * modules/relpath (Depends-on): Remove xalloc.
author Akim Demaille <akim@lrde.epita.fr>
date Mon, 18 Jun 2012 11:41:32 +0200
parents bb4ca9725d0a
children
line wrap: on
line source

/* relpath - print the relative path
   Copyright (C) 2012 Free Software Foundation, Inc.

   This program 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.

   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.  */

/* Written by Pádraig Brady.  */

#include <config.h>

#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#include "gettext.h"
#define _(msgid) gettext (msgid)

#include "canonicalize.h"
#include "dirname.h"
#include "error.h"
#include "relpath.h"

#include "pathmax.h"
#ifndef PATH_MAX
# define PATH_MAX 8192
#endif


/* Return the length of the longest common prefix
   of canonical PATH1 and PATH2, ensuring only full path components
   are matched.  Return 0 on no match.  */
static int _GL_ATTRIBUTE_PURE
path_common_prefix (const char *path1, const char *path2)
{
  int i = 0;
  int ret = 0;

  /* We already know path1[0] and path2[0] are '/'.  Special case
     '//', which is only present in a canonical name on platforms
     where it is distinct.  */
  if ((path1[1] == '/') != (path2[1] == '/'))
    return 0;

  while (*path1 && *path2)
    {
      if (*path1 != *path2)
        break;
      if (*path1 == '/')
        ret = i + 1;
      path1++;
      path2++;
      i++;
    }

  if ((!*path1 && !*path2)
      || (!*path1 && *path2 == '/')
      || (!*path2 && *path1 == '/'))
    ret = i;

  return ret;
}

/* Either output STR to stdout or
   if *PBUF is not NULL then append STR to *PBUF
   and update *PBUF to point to the end of the buffer
   and adjust *PLEN to reflect the remaining space.
   Return TRUE on failure.  */
static bool
buffer_or_output (const char* str, char **pbuf, size_t *plen)
{
  if (*pbuf)
    {
      size_t slen = strlen (str);
      if (slen >= *plen)
        return true;
      memcpy (*pbuf, str, slen + 1);
      *pbuf += slen;
      *plen -= slen;
    }
  else
    {
      fputs (str, stdout);
    }

  return false;
}


bool
relpath (const char *can_fname, const char *can_reldir, char *buf, size_t len)
{
  bool buf_err = false;

  /* Skip the prefix common to --relative-to and path.  */
  int common_index = path_common_prefix (can_reldir, can_fname);
  if (!common_index)
    return false;

  const char *relto_suffix = can_reldir + common_index;
  const char *fname_suffix = can_fname + common_index;

  /* Skip over extraneous '/'.  */
  if (*relto_suffix == '/')
    relto_suffix++;
  if (*fname_suffix == '/')
    fname_suffix++;

  /* Replace remaining components of --relative-to with '..', to get
     to a common directory.  Then output the remainder of fname.  */
  if (*relto_suffix)
    {
      buf_err |= buffer_or_output ("..", &buf, &len);
      for (; *relto_suffix; ++relto_suffix)
        {
          if (*relto_suffix == '/')
            buf_err |= buffer_or_output ("/..", &buf, &len);
        }

      if (*fname_suffix)
        {
          buf_err |= buffer_or_output ("/", &buf, &len);
          buf_err |= buffer_or_output (fname_suffix, &buf, &len);
        }
    }
  else
    {
      buf_err |= buffer_or_output (*fname_suffix ? fname_suffix : ".",
                                   &buf, &len);
    }

  if (buf_err)
    error (0, ENAMETOOLONG, "%s", _("generating relative path"));

  return !buf_err;
}

/* Return FROM represented as relative to the dir of TARGET.
   The result is malloced.  */

char *
convert_abs_rel (const char *from, const char *target)
{
  char *realtarget = NULL;
  char *realfrom = NULL;
  char *relative_from = NULL;
  char *res = NULL;

  realtarget = canonicalize_filename_mode (target, CAN_MISSING);
  if (!realtarget)
    goto end;
  realfrom = canonicalize_filename_mode (from, CAN_MISSING);
  if (!realfrom)
    goto end;

  /* Write to a PATH_MAX buffer.  */
  relative_from = malloc (PATH_MAX);
  if (!relative_from)
    {
      /* It's easier to set errno to ENOMEM than to rely on the
         'malloc-posix' gnulib module.  */
      errno = ENOMEM;
      goto end;
    }

  /* Get dirname to generate paths relative to.  */
  realtarget[dir_len (realtarget)] = '\0';

  if (!relpath (realfrom, realtarget, relative_from, PATH_MAX))
    {
      free (relative_from);
      relative_from = NULL;
    }
  res = relative_from ? relative_from : xstrdup (from);

 end:
  {
    int saved_errno = errno;
    free (realtarget);
    free (realfrom);
    errno = saved_errno;
  }
  return res;
}