/[coreutils]/coreutils/lib/chdir-long.c
ViewVC logotype

Diff of /coreutils/lib/chdir-long.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.3 by meyering, Sat Dec 11 11:19:27 2004 UTC revision 1.4 by meyering, Wed Jan 19 10:21:43 2005 UTC
# Line 1  Line 1 
1  /* provide a chdir function that tries not to fail due to ENAMETOOLONG  /* provide a chdir function that tries not to fail due to ENAMETOOLONG
2     Copyright (C) 2004 Free Software Foundation, Inc.     Copyright (C) 2004, 2005 Free Software Foundation, Inc.
3    
4     This program is free software; you can redistribute it and/or modify     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by     it under the terms of the GNU General Public License as published by
# Line 30  Line 30 
30  #include <assert.h>  #include <assert.h>
31  #include <limits.h>  #include <limits.h>
32    
 #include "mempcpy.h"  
33  #include "openat.h"  #include "openat.h"
34    
35  #ifndef O_DIRECTORY  #ifndef O_DIRECTORY
36  # define O_DIRECTORY 0  # define O_DIRECTORY 0
37  #endif  #endif
38    
 #ifndef MIN  
 # define MIN(a, b) ((a) < (b) ? (a) : (b))  
 #endif  
   
39  #ifndef PATH_MAX  #ifndef PATH_MAX
40  # error "compile this file only if your system defines PATH_MAX"  # error "compile this file only if your system defines PATH_MAX"
41  #endif  #endif
42    
 /* FIXME: this use of `MIN' is our sole concession to arbitrary limitations.  
    If, for some system, PATH_MAX is larger than 8191 and you call  
    chdir_long with a directory name that is longer than PATH_MAX,  
    yet that contains a single component that is more than 8191 bytes  
    long, then this function will fail.  */  
 #define MAX_COMPONENT_LENGTH MIN (PATH_MAX - 1, 8 * 1024)  
   
43  struct cd_buf  struct cd_buf
44  {  {
   /* FIXME maybe allocate this via malloc, rather than using the stack.  
      But that would be the sole use of malloc.  Is it worth it to  
      let chdir_long fail due to a low-memory condition?  
      But when using malloc, and assuming we remove the `concession'  
      above, we'll still have to avoid allocating 2^31 bytes on  
      systems that define PATH_MAX to very large number.  
      Ideally, we'd allocate enough to deal with most names, and  
      dynamically increase the buffer size only when necessary.  */  
   char buffer[MAX_COMPONENT_LENGTH + 1];  
   char *avail;  
45    int fd;    int fd;
46  };  };
47    
48  /* Like memchr, but return the number of bytes from MEM  static inline void
    to the first occurrence of C thereafter.  Search only  
    LEN bytes.  Return LEN if C is not found.  */  
 static inline size_t  
 memchrcspn (char const *mem, int c, size_t len)  
 {  
   char const *found = memchr (mem, c, len);  
   if (!found)  
     return len;  
   
   len = found - mem;  
   return len;  
 }  
   
 static void  
49  cdb_init (struct cd_buf *cdb)  cdb_init (struct cd_buf *cdb)
50  {  {
   cdb->avail = cdb->buffer;  
51    cdb->fd = AT_FDCWD;    cdb->fd = AT_FDCWD;
52  }  }
53    
 static inline bool  
 cdb_empty (struct cd_buf const *cdb)  
 {  
   return cdb->avail == cdb->buffer;  
 }  
   
54  static inline int  static inline int
55  cdb_fchdir (struct cd_buf const *cdb)  cdb_fchdir (struct cd_buf const *cdb)
56  {  {
57    return fchdir (cdb->fd);    return fchdir (cdb->fd);
58  }  }
59    
60    static inline void
61    cdb_free (struct cd_buf const *cdb)
62    {
63      if (0 <= cdb->fd)
64        {
65          bool close_fail = close (cdb->fd);
66          assert (! close_fail);
67        }
68    }
69    
70    /* Given a file descriptor of an open directory (or AT_FDCWD), CDB->fd,
71       try to open the CDB->fd-relative directory, DIR.  If the open succeeds,
72       update CDB->fd with the resulting descriptor, close the incoming file
73       descriptor, and return zero.  Upon failure, return -1 and set errno.  */
74  static int  static int
75  cdb_advance_fd (struct cd_buf *cdb, char const *dir)  cdb_advance_fd (struct cd_buf *cdb, char const *dir)
76  {  {
# Line 111  cdb_advance_fd (struct cd_buf *cdb, char Line 82  cdb_advance_fd (struct cd_buf *cdb, char
82          return -1;          return -1;
83      }      }
84    
85    if (cdb->fd != AT_FDCWD)    cdb_free (cdb);
     close (cdb->fd);  
86    cdb->fd = new_fd;    cdb->fd = new_fd;
87    
88    return 0;    return 0;
89  }  }
90    
91  static int  /* Return a pointer to the first non-slash in S.  */
92  cdb_flush (struct cd_buf *cdb)  static inline char *
93  {  find_non_slash (char const *s)
   if (cdb_empty (cdb))  
     return 0;  
   
   cdb->avail[0] = '\0';  
   if (cdb_advance_fd (cdb, cdb->buffer) != 0)  
     return -1;  
   
   cdb->avail = cdb->buffer;  
   
   return 0;  
 }  
   
 static void  
 cdb_free (struct cd_buf *cdb)  
94  {  {
95    if (0 <= cdb->fd && close (cdb->fd) != 0)    size_t n_slash = strspn (s, "/");
96      abort ();    return (char *) s + n_slash;
 }  
   
 static int  
 cdb_append (struct cd_buf *cdb, char const *s, size_t len)  
 {  
   char const *end = cdb->buffer + sizeof cdb->buffer;  
   
   /* Insert a slash separator if there is a preceding byte  
      and it's not a slash.  */  
   bool need_slash = (cdb->buffer < cdb->avail && cdb->avail[-1] != '/');  
   size_t n_free;  
   
   if (sizeof cdb->buffer < len + 1)  
     {  
       /* This single component is too long.  */  
       errno = ENAMETOOLONG;  
       return -1;  
     }  
   
   /* See if there's enough room for the `/', the new component and  
      a trailing NUL.  */  
   n_free = end - cdb->avail;  
   if (n_free < need_slash + len + 1)  
     {  
       if (cdb_flush (cdb) != 0)  
         return -1;  
       need_slash = false;  
     }  
   
   if (need_slash)  
     *(cdb->avail)++ = '/';  
   
   cdb->avail = mempcpy (cdb->avail, s, len);  
   return 0;  
97  }  }
98    
99  /* This is a wrapper around chdir that works even on PATH_MAX-limited  /* This is a function much like chdir, but without the PATH_MAX limitation
100     systems.  It handles an arbitrarily long directory name by extracting     on the length of the directory name.  A significant difference is that
101     and processing manageable portions of the name.  On systems without     it must be able to modify (albeit only temporarily) the directory
102     the openat syscall, this means changing the working directory to     name.  It handles an arbitrarily long directory name by operating
103     more and more `distant' points along the long directory name and     on manageable portions of the name.  On systems without the openat
104     then restoring the working directory.     syscall, this means changing the working directory to more and more
105     If any of those attempts to change or restore the working directory     `distant' points along the long directory name and then restoring
106     fails, this function exits nonzero.     the working directory.  If any of those attempts to save or restore
107       the working directory fails, this function exits nonzero.
108     Note that this function may still fail with errno == ENAMETOOLONG,  
109     but only if the specified directory name contains a component that     Note that this function may still fail with errno == ENAMETOOLONG, but
110     is long enough to provoke such a failure all by itself (e.g. if the     only if the specified directory name contains a component that is long
111     component is longer than PATH_MAX on systems that define PATH_MAX).  */     enough to provoke such a failure all by itself (e.g. if the component
112       has length PATH_MAX or greater on systems that define PATH_MAX).  */
113    
114  int  int
115  chdir_long (char const *dir)  chdir_long (char *dir)
116  {  {
117    int e = chdir (dir);    int e = chdir (dir);
118    if (e == 0 || errno != ENAMETOOLONG)    if (e == 0 || errno != ENAMETOOLONG)
# Line 197  chdir_long (char const *dir) Line 120  chdir_long (char const *dir)
120    
121    {    {
122      size_t len = strlen (dir);      size_t len = strlen (dir);
123      char const *dir_end = dir + len;      char *dir_end = dir + len;
     char const *d;  
124      struct cd_buf cdb;      struct cd_buf cdb;
125        size_t n_leading_slash;
126    
127      cdb_init (&cdb);      cdb_init (&cdb);
128    
129      /* If DIR is the empty string, then the chdir above      /* If DIR is the empty string, then the chdir above
130         must have failed and set errno to ENOENT.  */         must have failed and set errno to ENOENT.  */
131      assert (0 < len);      assert (0 < len);
132        assert (PATH_MAX <= len);
133    
134        /* Count leading slashes.  */
135        n_leading_slash = strspn (dir, "/");
136    
137      if (*dir == '/')      /* Handle any leading slashes as well as any name that matches
138           the regular expression, m!^//hostname[/]*! .  Handling this
139           prefix separately usually results in a single additional
140           cdb_advance_fd call, but it's worthwhile, since it makes the
141           code in the following loop cleaner.  */
142        if (n_leading_slash == 2)
143        {        {
144          /* Names starting with exactly two slashes followed by at least          int err;
145             one non-slash are special --          /* Find next slash.
146             for example, in some environments //Hostname/file may             We already know that dir[2] is neither a slash nor '\0'.  */
147             denote a file on a different host.          char *slash = memchr (dir + 3, '/', dir_end - (dir + 3));
148             Preserve those two leading slashes.  Treat all other          if (slash == NULL)
            sequences of slashes like a single one.  */  
         if (3 <= len && dir[1] == '/' && dir[2] != '/')  
149            {            {
150              size_t name_len = 1 + strcspn (dir + 3, "/");              errno = ENAMETOOLONG;
151              if (cdb_append (&cdb, dir, 2 + name_len) != 0)              return -1;
               goto Fail;  
             /* Advance D to next slash or to end of string. */  
             d = dir + 2 + name_len;  
             assert (*d == '/' || *d == '\0');  
           }  
         else  
           {  
             if (cdb_append (&cdb, "/", 1) != 0)  
               goto Fail;  
             d = dir + 1;  
152            }            }
153            *slash = '\0';
154            err = cdb_advance_fd (&cdb, dir);
155            *slash = '/';
156            if (err != 0)
157              goto Fail;
158            dir = find_non_slash (slash + 1);
159        }        }
160      else      else if (n_leading_slash)
161        {        {
162          d = dir;          if (cdb_advance_fd (&cdb, "/") != 0)
163              goto Fail;
164            dir += n_leading_slash;
165        }        }
166    
167      while (1)      assert (*dir != '/');
168        assert (dir <= dir_end);
169    
170        while (PATH_MAX <= dir_end - dir)
171        {        {
172          /* Skip any slashes to find start of next component --          int err;
173             or the end of DIR. */          /* Find a slash that is PATH_MAX or fewer bytes away from dir.
174          char const *start = d + strspn (d, "/");             I.e. see if there is a slash that will give us a name of
175          if (*start == '\0')             length PATH_MAX-1 or less.  */
176            {          char *slash = memrchr (dir, '/', PATH_MAX);
177              if (cdb_flush (&cdb) != 0)          if (slash == NULL)
               goto Fail;  
             break;  
           }  
         /* If the remaining portion is no longer than PATH_MAX, then  
            flush anything that is buffered and do the rest in one chunk.  */  
         if (dir_end - start <= PATH_MAX)  
178            {            {
179              if (cdb_flush (&cdb) != 0              errno = ENAMETOOLONG;
180                  || cdb_advance_fd (&cdb, start) != 0)              return -1;
               goto Fail;  
             break;  
181            }            }
182    
183          len = memchrcspn (start, '/', dir_end - start);          *slash = '\0';
184          assert (len == strcspn (start, "/"));          assert (slash - dir < PATH_MAX);
185          d = start + len;          err = cdb_advance_fd (&cdb, dir);
186          if (cdb_append (&cdb, start, len) != 0)          *slash = '/';
187            if (err != 0)
188              goto Fail;
189    
190            dir = find_non_slash (slash + 1);
191          }
192    
193        if (dir < dir_end)
194          {
195            if (cdb_advance_fd (&cdb, dir) != 0)
196            goto Fail;            goto Fail;
197        }        }
198    
# Line 318  main (int argc, char *argv[]) Line 250  main (int argc, char *argv[])
250      error (EXIT_FAILURE, errno,      error (EXIT_FAILURE, errno,
251             "chdir_long failed: %s", line);             "chdir_long failed: %s", line);
252    
253    {    if (argc <= 1)
254      /* Using `pwd' here makes sense only if it is a robust implementation,      {
255         like the one in coreutils after the 2004-04-19 changes.  */        /* Using `pwd' here makes sense only if it is a robust implementation,
256      char const *cmd = "pwd";           like the one in coreutils after the 2004-04-19 changes.  */
257      execlp (cmd, (char *) NULL);        char const *cmd = "pwd";
258      error (EXIT_FAILURE, errno, "%s", cmd);        execlp (cmd, (char *) NULL);
259    }        error (EXIT_FAILURE, errno, "%s", cmd);
260        }
261    
262      fclose (stdin);
263      fclose (stderr);
264    
265    /* not reached */    exit (EXIT_SUCCESS);
   abort ();  
266  }  }
267  #endif  #endif
268    

Legend:
Removed from v.1.3  
changed lines
  Added in v.1.4

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26