1  /* Copyright (C) 1991-2023 Free Software Foundation, Inc.
       2     This file is part of the GNU C Library.
       3  
       4     The GNU C Library is free software; you can redistribute it and/or
       5     modify it under the terms of the GNU Lesser General Public
       6     License as published by the Free Software Foundation; either
       7     version 2.1 of the License, or (at your option) any later version.
       8  
       9     The GNU C Library is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12     Lesser General Public License for more details.
      13  
      14     You should have received a copy of the GNU Lesser General Public
      15     License along with the GNU C Library; if not, see
      16     <https://www.gnu.org/licenses/>.  */
      17  
      18  #include <errno.h>
      19  #include <sys/types.h>
      20  #include <sys/stat.h>
      21  #include <hurd.h>
      22  #include <hurd/port.h>
      23  #include <dirent.h>
      24  #include <unistd.h>
      25  #include <stdlib.h>
      26  #include <string.h>
      27  #include <stdio.h>
      28  #include <fcntl.h>
      29  
      30  
      31  /* Get the canonical absolute name of the given directory port, and put it
      32     in SIZE bytes of BUF.  Returns NULL if the directory couldn't be
      33     determined or SIZE was too small.  If successful, returns BUF.  In GNU,
      34     if BUF is NULL, an array is allocated with `malloc'; the array is SIZE
      35     bytes long, unless SIZE <= 0, in which case it is as big as necessary.
      36     If our root directory cannot be reached, the result will not begin with
      37     a slash to indicate that it is relative to some unknown root directory.  */
      38  
      39  char *
      40  __hurd_canonicalize_directory_name_internal (file_t thisdir,
      41  					    char *buf,
      42  					    size_t size)
      43  {
      44    error_t err;
      45    mach_port_t rootid, thisid, rootdevid, thisdevid;
      46    ino64_t rootino, thisino;
      47    char *file_name;
      48    char *file_namep;
      49    file_t parent;
      50    char *dirbuf = NULL;
      51    unsigned int dirbufsize = 0;
      52    const size_t orig_size = size;
      53  
      54    inline void cleanup (void)
      55      {
      56        if (parent != thisdir)
      57  	__mach_port_deallocate (__mach_task_self (), parent);
      58  
      59        __mach_port_deallocate (__mach_task_self (), thisid);
      60        __mach_port_deallocate (__mach_task_self (), thisdevid);
      61        __mach_port_deallocate (__mach_task_self (), rootid);
      62  
      63        if (dirbuf != NULL)
      64  	__vm_deallocate (__mach_task_self (),
      65  			 (vm_address_t) dirbuf, dirbufsize);
      66      }
      67  
      68  
      69    if (size <= 0)
      70      {
      71        if (buf != NULL)
      72          return __hurd_fail (EINVAL), NULL;
      73  
      74        size = FILENAME_MAX * 4 + 1;	/* Good starting guess.  */
      75      }
      76  
      77    if (buf != NULL)
      78      file_name = buf;
      79    else
      80      {
      81        file_name = malloc (size);
      82        if (file_name == NULL)
      83  	return NULL;
      84      }
      85  
      86    file_namep = file_name + size;
      87    *--file_namep = '\0';
      88  
      89    /* Get a port to our root directory and get its identity.  */
      90  
      91    if (err = __USEPORT (CRDIR, __io_identity (port,
      92  					     &rootid, &rootdevid, &rootino)))
      93      return __hurd_fail (err), NULL;
      94    __mach_port_deallocate (__mach_task_self (), rootdevid);
      95  
      96    /* Stat the port to the directory of interest.  */
      97  
      98    if (err = __io_identity (thisdir, &thisid, &thisdevid, &thisino))
      99      {
     100        __mach_port_deallocate (__mach_task_self (), rootid);
     101        return __hurd_fail (err), NULL;
     102      }
     103  
     104    parent = thisdir;
     105    while (thisid != rootid)
     106      {
     107        /* PARENT is a port to the directory we are currently on;
     108  	 THISID, THISDEV, and THISINO are its identity.
     109  	 Look in its parent (..) for a file with the same file number.  */
     110  
     111        struct dirent64 *d;
     112        mach_port_t dotid, dotdevid;
     113        ino64_t dotino;
     114        int mount_point;
     115        file_t newp;
     116        char *dirdata;
     117        mach_msg_type_number_t dirdatasize;
     118        int direntry, nentries;
     119  
     120  
     121        /* Look at the parent directory.  */
     122        newp = __file_name_lookup_under (parent, "..", O_READ, 0);
     123        if (newp == MACH_PORT_NULL)
     124  	goto lose;
     125        if (parent != thisdir)
     126  	__mach_port_deallocate (__mach_task_self (), parent);
     127        parent = newp;
     128  
     129        /* Get this directory's identity and figure out if it's a mount
     130           point.  */
     131        if (err = __io_identity (parent, &dotid, &dotdevid, &dotino))
     132  	goto errlose;
     133        mount_point = dotdevid != thisdevid;
     134  
     135        if (thisid == dotid)
     136  	{
     137  	  /* `..' == `.' but it is not our root directory.  */
     138  	  __mach_port_deallocate (__mach_task_self (), dotid);
     139  	  __mach_port_deallocate (__mach_task_self (), dotdevid);
     140  	  break;
     141  	}
     142  
     143        /* Search for the last directory.  */
     144        direntry = 0;
     145        dirdata = dirbuf;
     146        dirdatasize = dirbufsize;
     147        while (!(err = __dir_readdir (parent, &dirdata, &dirdatasize,
     148  				    direntry, -1, 0, &nentries))
     149  	     && nentries != 0)
     150  	{
     151  	  /* We have a block of directory entries.  */
     152  
     153  	  unsigned int offset;
     154  
     155  	  direntry += nentries;
     156  
     157  	  if (dirdata != dirbuf)
     158  	    {
     159  	      /* The data was passed out of line, so our old buffer is no
     160  		 longer useful.  Deallocate the old buffer and reset our
     161  		 information for the new buffer.  */
     162  	      __vm_deallocate (__mach_task_self (),
     163  			       (vm_address_t) dirbuf, dirbufsize);
     164  	      dirbuf = dirdata;
     165  	      dirbufsize = round_page (dirdatasize);
     166  	    }
     167  
     168  	  /* Iterate over the returned directory entries, looking for one
     169  	     whose file number is THISINO.  */
     170  
     171  	  offset = 0;
     172  	  while (offset < dirdatasize)
     173  	    {
     174  	      d = (struct dirent64 *) &dirdata[offset];
     175  	      offset += d->d_reclen;
     176  
     177  	      /* Ignore `.' and `..'.  */
     178  	      if (d->d_name[0] == '.'
     179  		  && (d->d_namlen == 1
     180  		      || (d->d_namlen == 2 && d->d_name[1] == '.')))
     181  		continue;
     182  
     183  	      if (mount_point || d->d_ino == thisino)
     184  		{
     185  		  file_t try = __file_name_lookup_under (parent, d->d_name,
     186  							 O_NOLINK, 0);
     187  		  file_t id, devid;
     188  		  ino64_t fileno;
     189  		  if (try == MACH_PORT_NULL)
     190  		    goto lose;
     191  		  err = __io_identity (try, &id, &devid, &fileno);
     192  		  __mach_port_deallocate (__mach_task_self (), try);
     193  		  if (err)
     194  		    goto inner_errlose;
     195  		  __mach_port_deallocate (__mach_task_self (), id);
     196  		  __mach_port_deallocate (__mach_task_self (), devid);
     197  		  if (id == thisid)
     198  		    goto found;
     199  		}
     200  	    }
     201  	}
     202  
     203        if (err)
     204  	{
     205  	inner_errlose:		/* Goto ERRLOSE: after cleaning up.  */
     206  	  __mach_port_deallocate (__mach_task_self (), dotid);
     207  	  __mach_port_deallocate (__mach_task_self (), dotdevid);
     208  	  goto errlose;
     209  	}
     210        else if (nentries == 0)
     211  	{
     212  	  /* We got to the end of the directory without finding anything!
     213  	     We are in a directory that has been unlinked, or something is
     214  	     broken.  */
     215  	  err = ENOENT;
     216  	  goto inner_errlose;
     217  	}
     218        else
     219        found:
     220  	{
     221  	  /* Prepend the directory name just discovered.  */
     222  	  size_t offset = file_namep - file_name;
     223  
     224  	  if (offset < d->d_namlen + 1)
     225  	    {
     226  	      if (orig_size > 0)
     227  		return __hurd_fail (ERANGE), NULL;
     228  	      else
     229  		{
     230  		  size *= 2;
     231  		  buf = realloc (file_name, size);
     232  		  if (buf == NULL)
     233  		    {
     234  		      free (file_name);
     235  		      return NULL;
     236  		    }
     237  		  file_namep = &buf[offset + size / 2];
     238  		  file_name = buf;
     239  		  /* Move current contents up to the end of the buffer.
     240  		     This is guaranteed to be non-overlapping.  */
     241  		  memcpy (file_namep, file_namep - size / 2,
     242  			  file_name + size - file_namep);
     243  		}
     244  	    }
     245  	  file_namep -= d->d_namlen;
     246  	  (void) memcpy (file_namep, d->d_name, d->d_namlen);
     247  	  *--file_namep = '/';
     248  	}
     249  
     250        /* The next iteration will find the name of the directory we
     251  	 just searched through.  */
     252        __mach_port_deallocate (__mach_task_self (), thisid);
     253        __mach_port_deallocate (__mach_task_self (), thisdevid);
     254        thisid = dotid;
     255        thisdevid = dotdevid;
     256        thisino = dotino;
     257      }
     258  
     259    if (file_namep == &file_name[size - 1])
     260      /* We found nothing and got all the way to the root.
     261         So the root is our current directory.  */
     262      *--file_namep = '/';
     263  
     264    memmove (file_name, file_namep, file_name + size - file_namep);
     265    cleanup ();
     266    return file_name;
     267  
     268   errlose:
     269    /* Set errno.  */
     270    (void) __hurd_fail (err);
     271   lose:
     272    if (orig_size == 0)
     273      free (file_name);
     274    cleanup ();
     275    return NULL;
     276  }
     277  strong_alias (__hurd_canonicalize_directory_name_internal, _hurd_canonicalize_directory_name_internal)
     278  
     279  char *
     280  __canonicalize_directory_name_internal (const char *thisdir, char *buf,
     281  					size_t size)
     282  {
     283    char *result;
     284    file_t port = __file_name_lookup (thisdir, 0, 0);
     285    if (port == MACH_PORT_NULL)
     286      return NULL;
     287    result = __hurd_canonicalize_directory_name_internal (port, buf, size);
     288    __mach_port_deallocate (__mach_task_self (), port);
     289    return result;
     290  }
     291  
     292  /* Get the pathname of the current working directory, and put it in SIZE
     293     bytes of BUF.  Returns NULL if the directory couldn't be determined or
     294     SIZE was too small.  If successful, returns BUF.  In GNU, if BUF is
     295     NULL, an array is allocated with `malloc'; the array is SIZE bytes long,
     296     unless SIZE <= 0, in which case it is as big as necessary.  */
     297  char *
     298  __getcwd (char *buf, size_t size)
     299  {
     300    char *cwd =
     301      __USEPORT (CWDIR,
     302  	       __hurd_canonicalize_directory_name_internal (port,
     303  							    buf, size));
     304    return cwd;
     305  }
     306  libc_hidden_def (__getcwd)
     307  weak_alias (__getcwd, getcwd)