/** * Copyright (c) Members of the EGEE Collaboration. 2010. * See http://www.eu-egee.org/partners/ for details on the copyright * holders. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Authors: Oscar Koeroo, Mischa Sall\'e, Aram Verstegen * NIKHEF Amsterdam, the Netherlands * */ #include #include #include #include #include #include #include #include #include /* Make sure at least internally we have a PATH_MAX */ #ifndef PATH_MAX #define PATH_MAX 1024 #endif /* Make sure at least internally we have a SYMLOOP_MAX */ #ifndef SYMLOOP_MAX #define SYMLOOP_MAX _POSIX_SYMLOOP_MAX #endif /*! * Returns the fully canonicalized and absolute path for a diretory or NULL on * error. Memory for the return string is malloced and should be freed by the * caller. Any path cannot be longer than PATH_MAX. Note that it is necessary * that we can chdir to dir. * NOTE: This function is not thread-safe * \param dir non-absolute and/or non-canonical directory * \return canonical absolute directory */ char *cgul_realdir(const char *dir) { int olddir; char buf[PATH_MAX],*path; /* Safe old dir and then go to the new one */ if ( (olddir=open(".",O_RDONLY))==-1 || chdir(dir)==-1 ) return NULL; /* Get the location, don't exit yet when failed: need to go back */ path=getcwd(buf,PATH_MAX); /* Go back, note that it's really bad if this fails! */ if ( fchdir(olddir) || close(olddir) ) return NULL; /* Make a copy */ if (path) path=strdup(buf); return path; } /*! * Returns the fully canonicalized and absolute path for any path or NULL on * error. Note that this function substitutes the unsafe realpath() or the * GNU-only canonicalize_file_name(). * Memory for the return string is malloced and should be freed by the * caller. Any path cannot be longer than PATH_MAX. Any directory has to be * accessible, including if it's the last element. * NOTE: This function is not thread-safe * \param inpath non-absolute and/or non-canonical path * \param instat optional stat information on the input (for performance, if * already available). When NULL it will be determined locally * when needed. * \return canonical absolute path */ char *cgul_realpath(const char *inpath, struct stat *instat) { char buf[PATH_MAX], path[PATH_MAX]; char *pos,*canonicdir; int pathlen,filelen,linklen, linkcount=0; struct stat st; /* Check and store the length of the input (incl \0) */ if ( (pathlen=1+strlen(inpath)) > PATH_MAX) { errno=ENAMETOOLONG; return NULL; } /* Check last compo is dir: then just return realdir() */ if (instat==NULL) { if (stat(inpath, &st)==-1) /* Error */ return NULL; instat=&st; } if (S_ISDIR(instat->st_mode)) return cgul_realdir(inpath); /* Make path absolute */ if (inpath[0]!='/') { /* relative */ /* get and add cwd */ if (!getcwd(buf,PATH_MAX)) { if (errno==ERANGE) errno=ENAMETOOLONG; return NULL; } if ( (pathlen=snprintf(path,PATH_MAX,"%s/%s",buf,inpath))>PATH_MAX ) { errno=ENAMETOOLONG; return NULL; } } else /* absolute */ strcpy(path,inpath); /* Now loop over symlink, if any */ /* readlink wants buffersize without \0: PATH_MAX-1 */ while ( (linklen=readlink(path,buf,PATH_MAX-1)) != -1 ) { if (++linkcount > SYMLOOP_MAX) { /* Too many symlinks */ errno=ELOOP; return NULL; } buf[linklen]='\0'; /* Check the symlink */ if (buf[0]=='/') /* absolute: replaces entire path */ strcpy(path,buf); else { /* relative: replaces last component */ pos=strrchr(path,'/'); /* Has to exist, we made it absolute */ filelen=strlen(pos+1); /* length of remainder without \0 */ /* Note: pathlen is with \0, filelen and linklen without, hence * combined as follows is with \0 and should be <= PATH_MAX */ if (pathlen-filelen+linklen>PATH_MAX) { errno=ENAMETOOLONG; return NULL; } strcpy(pos+1,buf); /* It fits, so use strcpy */ } } /* Alway end due to realink: check it's due to non-symlink */ if (errno!=EINVAL) /* i.e. other error */ return NULL; /* Now path is not a symlink */ /* Find dir part */ pos=strrchr(path,'/'); /* Has to exist, we made it absolute */ /* dir: up to pos, file: pos+1 till end */ pos[0]='\0'; canonicdir=cgul_realdir(path); pos[0]='/'; /* Clean but unnecessary */ /* Put them together, canonicdir doesn't end with a / */ pathlen=snprintf(buf,PATH_MAX,"%s/%s",canonicdir,pos+1); free(canonicdir); /* Done with canonicdir */ if (pathlen>PATH_MAX) { errno=ENAMETOOLONG; return NULL; } /* Return duplicate, or NULL when fails */ return strdup(buf); }