/** * Copyright (c) FOM-Nikhef 2014- * * 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: * 2014- * Mischa Sall\'e * NIKHEF Amsterdam, the Netherlands * * */ #define _XOPEN_SOURCE 500 #include #include #include #include #include #include #include #include #include #include #include #include #include "lcmaps_gridmapfile.h" #include "lcmaps_gridmapdir.h" /************************************************************************ * defines ************************************************************************/ #define LOGSTR_PREFIX "lcmaps_gridmapdir-" #define RETRY_MAX 3 /* how many retries for creating hardlink */ /************************************************************************ * private prototypes ************************************************************************/ /* Find the default gridmapdir: try GRIDMAPDIR env variable. If unset, error. * default_dir needs to be freed. * return 0 on success, -1 on error */ static int get_default_mapdir(char **default_dir); /* Checks whether mapping is a valid pool-entry for given pool. * Return 1 on success, 0 on no match and -1 on error */ static int pool_match(const char *mapping, const char *pool, int options); /* Add the mapcount to the leasename. * NOTE: to separate the pure DN from attached strings we insert the character * '\001' after the DN. The url encoding (in lcmaps_gridmapfile()) will be * done up to this point. * The '\001' character may already be there if a gidstring was inserted * first, so this is checked for. In that case a ':' is inserted. * return 0 on success, -1 on error */ static int add_mapcount_to_leasename(int mapcounter, const char *oldlease, char **newlease); /* Creates a URL encoded copy (anything non-alnum is encoded) of the input idp. * The result in encodedidp needs to be freed. * Return 0 on success, -1 on error */ static int urlencode(const char *idp, char **encodedidp); /* Tries to make the requested pool mapping in the (grid)mapdir for given * encoded(globus)idp. * When options contains ONLY_USE_EXISTING_LEASE no new lease will be created. * When options contains OVERRIDE_INCONSISTANCY an existing hardlink will be * replaced. * return 1 on succes, 0 on no pool-entry, -1 on error */ static int get_req_pool_mapping(const char *mapdir, const char *encodedidp, const char *req_mapping, int options); /* Tries to find a pool mapping in the (grid)mapdir for given encoded(globus)idp * and pool-name. * When options contains ONLY_USE_EXISTING_LEASE no new lease will be created. * When options contains OVERRIDE_INCONSISTANCY an existing hardlink will be * replaced. Upon success the resulting pool-entry is returned in mapping, which * needs to be freed. * return 1 on succes, 0 on no mapping, -1 on error */ static int get_pool_mapping(const char *mapdir, const char *encodedidp, const char *poolname, int options, char **mapping); /* Prefixes name with mapdir. fullname needs to be freed. * return 0 on success, -1 on error */ static int get_fullname(const char *mapdir, const char *name, char **fullname); /* Finds other hardlink to given firstlink, which is at given inode. Note: this * is a relatively expensive operation as we cycle through the gridmapdir. * return 2 when found, 1 when only firstlink was found, 0 when neither is * found, -1 on error */ static int get_otherlink(const char *mapdir, const char *firstlink, ino_t firstinode, char **otherlink); /* Creates a new link or reuses a valid existing link from mapping to lease. * The result should have requested inode req_inode. mapping_filename and * lease_filename should be absolute path names. mapping and lease are the * relative filenames, only used for logging. In case linking failed due to * existing lease which then disappears, we retry (max. RETRY_MAX times) * linking. When link() succeeds and the link is valid, we touch it using * utime() to keep track of the usage. * return 1 on success, 0 on other link went in between, -1 on error */ static int create_link(const char *mapping, const char *mapping_filename, ino_t req_inode, const char *lease, const char *lease_filename); /************************************************************************ * public functions ************************************************************************/ /** * Using a grid-mapfile (globusidfile) and grid-mapdir it looks for a DN or FQAN * (globusidp) in the grid-mapfile for a pool, i.e. a mapping starting with a . * (a period). When a pool is found it uses the grid-mapdir to find a valid * existing lease or tries to create a new lease. When a requested mapping is * specified the resulting mapping (pool-entry) has to match it. * The encoded gridmapdir entry is based on leasename (when set), or otherwise * globusidp. When a valid mapcounter is set (MAPPING_MIN <= mapcounter * <=mapping_max) it will be added to the leasename to create the encoded * entry in the gridmapdir. The mapping_max value should MAPPING_MIN <= * mapping_max <= ABSOLUTE_MAPPING_MAX or UNSET_MAPPING_MAX. When a valid lease * is obtained, the resulting pool-entry is returned in pool-entry, the encoded * hardlink in encodedglobusidp, both should be freed. * return 1 on successful mapping, 0 on no match or mapping, -1 on error */ int lcmaps_gridmapdir(const char *globusidfile, const char *globusidp, const char *gridmapdir, int mapping_max, int mapcounter, const char *leasename, const char *req_mapping, int options, char **poolentry, char **encodedglobusidp) { const char *mapdir=gridmapdir; char *default_mapdir=NULL; const char *lease=NULL; char *newlease=NULL; char *mapping=NULL, *poolname=NULL, *encodedidp=NULL, *poolmapping=NULL; int rc=-1; /* default error */ /* protect */ if (!globusidp || !poolentry || !encodedglobusidp) return -1; /* Did we get a valid gridmapdir: * grid-mapfile can be unset, gridmapdir not: we use env var if unset */ if (mapdir == NULL || mapdir[0]=='\0') { if (get_default_mapdir(&default_mapdir)==-1) return -1; mapdir=default_mapdir; } /* Check the mapcounter against maximum number of allowed mappings * The following scenarios may happen: * 1. mapping_max is not set (==UNSET_MAPPING_MAX) -> effectively 1 * a. mapcounter is not set (==-1, 0) * Legacy: one mapping is allowed, not reflected in the poolindex. * b. mapcounter is set (>0): * Enforce only one mapping with mapcounter is 1, reflected in the * poolindex. If mapcounter>1: error. * 2. mapping_max is set (>UNSET_MAPPING_MAX), * If mapping_max==0: error * a. mapcounter is not set * No action * b. mapcounter is set (>0) * Mapping is allowed, if mapcounter <= mapping_max */ if (mapping_max==UNSET_MAPPING_MAX) mapping_max=MAPPING_MIN; /* default: MAPPING_MIN */ else if (mapping_max < MAPPING_MIN || mapping_max > ABSOLUTE_MAPPING_MAX) { /* invalid value */ lcmaps_log(LOG_ERR, "%s: Illegal value for maximum mappings per credential\" (%d). " "Should at least be %d\n", __func__, mapping_max, MAPPING_MIN); goto cleanup; } /* Now check mapcounter */ if (mapcounter!=-1 && mapcounter!=0 && mapcounter mapping_max) { lcmaps_log(LOG_ERR, "%s: the request for mapping nr. %d, exceeds the maximum of %d\n", __func__, mapcounter, mapping_max); goto cleanup; } /* log the dir */ lcmaps_log(LOG_DEBUG, "%s: Using mapdir %s\n", __func__, mapdir); /* add substring matching to options */ options|=MATCH_INCLUDE; /* try to get mapping from grid-mapfile */ rc=lcmaps_gridmapfile(globusidfile, globusidp, ".", options, &mapping); if (rc==-1 || rc==0) /* only valid rc==0 is default user, that cannot be for gridmapdir */ goto cleanup; /* poolname starts just after the . */ poolname=mapping+1; lcmaps_log(LOG_DEBUG, "%s: Found pool %s for \"%s\"\n", __func__, poolname, globusidp); /* For a requested mapping, check here that the poolname matches the * requested mapping */ if (req_mapping) { rc=pool_match(req_mapping, poolname, options); if (rc==0) { rc=-1; /* no match -> error */ lcmaps_log(LOG_NOTICE, "%s: requested mapping \"%s\" is not in pool \"%s\"\n", __func__, req_mapping, poolname); } if (rc==-1) goto cleanup; } /* If we have a leasename, add the map counter */ if (leasename) { if (mapcounter'9') return 0; } } /* successful match */ return 1; } /** * Add the mapcount to the leasename. * NOTE: to separate the pure DN from attached strings we insert the character * '\001' after the DN. The url encoding (in lcmaps_gridmapfile()) will be * done up to this point. * The '\001' character may already be there if a gidstring was inserted * first, so this is checked for. In that case a ':' is inserted. * return 0 on success, -1 on error */ static int add_mapcount_to_leasename(int mapcounter, const char *oldlease, char **newlease) { const char *logstr=LOGSTR_PREFIX"add_mapcount_to_leasename"; char * lease = NULL, separator; size_t leaselen; /* OK, create a new lease with a ':mapcount=' at the end */ leaselen=strlen(oldlease)+15; if ( (lease=calloc(leaselen,1))==NULL ) { lcmaps_log(LOG_ERR,"%s: out of memory\n", logstr); return -1; } if (strstr(oldlease, "\001")) separator=':'; else separator='\001'; if (snprintf(lease,leaselen,"%s%cmapcount=%04d", oldlease,separator,mapcounter)<0) { lcmaps_log(LOG_ERR,"%s: snprintf() failed: %s\n", logstr, strerror(errno)); free(lease); return -1; } lcmaps_log(LOG_DEBUG, "%s: leasename before adding mapcount: %s\n", logstr, oldlease); lcmaps_log(LOG_DEBUG, "%s: lease after adding mapcount: %s\n", logstr, lease); *newlease=lease; return 0; } /** * Creates a URL encoded copy (anything non-alnum is encoded) of the input idp. * The result in encodedidp needs to be freed. * Return 0 on success, -1 on error */ static int urlencode(const char *idp, char **encodedidp) { const char *logstr=LOGSTR_PREFIX"urlencode"; size_t i,j,len; char *buffer=NULL; /* sanity check */ if (!idp && !encodedidp) return -1; /* each char becomes at most 3 chars (%HL) plus 1 \0 byte. Use calloc to * automatically \0 terminate buffer */ len=3*strlen(idp)+1; if ( (buffer=calloc(1,len)) == NULL) { lcmaps_log(LOG_ERR,"%s: out of memory\n", logstr); return -1; } for (i=j=0; idp[i]; i++, j++) { if (isalnum(idp[i])) /* copy over */ buffer[j]=(char)tolower(idp[i]); else if (idp[i] == '\001') { /* Special: replace with : and add rest of input */ buffer[j++]=':'; strncpy(buffer+j,idp+i+1,len-j); break; } else { /* Need to copy over encoded */ snprintf(buffer+j,len-j,"%%%02x", idp[i]); j+=2; /* 2 extra bytes */ } } /* set output field */ *encodedidp=buffer; return 0; } /** * Tries to make the requested pool mapping in the (grid)mapdir for given * encoded(globus)idp. * When options contains ONLY_USE_EXISTING_LEASE no new lease will be created. * When options contains OVERRIDE_INCONSISTANCY an existing hardlink will be * replaced. * return 1 on succes, 0 on no or wrong pool-entry, -1 on error */ static int get_req_pool_mapping(const char *mapdir, const char *encodedidp, const char *req_mapping, int options) { const char *logstr=LOGSTR_PREFIX"get_req_pool_mapping"; char *encodedfilename=NULL, *mappingfilename=NULL, *otherlink=NULL; struct stat st_mapping, st_lease; int remove_lease=0, retval=-1; /* default error */ /* get absolute filenames for lease and requested mapping */ if (get_fullname(mapdir, encodedidp, &encodedfilename)<0 || get_fullname(mapdir, req_mapping, &mappingfilename)<0) { free(encodedfilename); return -1; } /* get stat info for lease: may fail with a ENOENT, only for run-mode */ if (lstat(encodedfilename, &st_lease)<0) { /* for verify mode: any error is fatal, for run mode: ENOENT is ok */ if ( (options & ONLY_USE_EXISTING_LEASE)!= 0 || errno!=ENOENT) { lcmaps_log(LOG_ERR, "%s: cannot stat lease \"%s\": %s\n", logstr, encodedfilename, strerror(errno)); goto cleanup; } /* lease does not exist */ st_lease.st_nlink=0; } else { /* sanity checks on existing lease: regular file, link count<=2 */ if (!S_ISREG(st_lease.st_mode)) { lcmaps_log(LOG_ERR, "%s: lease is not a regular file: \"%s\"\n", logstr, encodedidp); goto cleanup; } /* if link count lease > 2 -> error */ if (st_lease.st_nlink>2) { lcmaps_log(LOG_ERR, "%s: too many (%lu) hard-links for lease \"%s\"\n", logstr, (long unsigned)(st_lease.st_nlink),encodedidp); goto cleanup; } } /* get stat info for req_mapping: must exist */ if (lstat(mappingfilename, &st_mapping)<0) { lcmaps_log(LOG_ERR, "%s: cannot stat requested mapping %s: %s\n", logstr, mappingfilename, strerror(errno)); goto cleanup; } /* sanity checks on requested mapping: regular file and link count <=2 */ if (!S_ISREG(st_mapping.st_mode)) { lcmaps_log(LOG_ERR, "%s: pool-entry %s is not a regular file\n", logstr, req_mapping); goto cleanup; } /* if link count mapping > 2 -> error */ if (st_mapping.st_nlink>2) { lcmaps_log(LOG_ERR, "%s: too many (%lu) hard-links for pool-entry %s\n", logstr, (long unsigned)(st_mapping.st_nlink), req_mapping); goto cleanup; } /* mapping link count is either 1 or 2 */ if (st_mapping.st_nlink==2) { /* mapping link count 2: must match, otherwise error */ if (st_lease.st_nlink==2 && st_mapping.st_ino==st_lease.st_ino) { /* success: we found valid existing mapping */ lcmaps_log(LOG_DEBUG, "%s: reusing existing lease \"%s\" to %s\n", logstr, encodedidp, req_mapping); /* in normal run mode: touch existing lease */ if ( (options & ONLY_USE_EXISTING_LEASE)== 0 && utime(mappingfilename, NULL)<0) { lcmaps_log(LOG_NOTICE, "%s: touching requested pool-entry %s failed: %s\n", logstr, mappingfilename, strerror(errno)); goto cleanup; } retval=1; goto cleanup; } /* pool-entry is taken but not by correct lease: get it for log */ get_otherlink(mapdir, req_mapping, st_mapping.st_ino, &otherlink); lcmaps_log(LOG_NOTICE, "%s: pool-entry %s is taken by another lease: \"%s\"\n", logstr, req_mapping, otherlink ? otherlink : "unknown"); retval=0; goto cleanup; } /* mapping link count is 1: verify mode error, run mode fine. * For verify mode we still make distinction for logging different error. * lease link count is either 1 or 2 */ if (st_lease.st_nlink==2) { /* lease link count is 2: linked to wrong pool-entry: get it for log */ get_otherlink(mapdir, encodedidp, st_lease.st_ino, &otherlink); /* when stuck with this lease: error */ if ( (options & ONLY_USE_EXISTING_LEASE)!= 0 || (options & OVERRIDE_INCONSISTANCY) == 0 ) { lcmaps_log(LOG_NOTICE, "%s: lease is already hard-linked to wrong pool-" "entry %s instead of requested %s: \"%s\"\n", logstr, otherlink ? otherlink : "unknown", req_mapping, encodedidp); retval=0; goto cleanup; } /* we need to remove the inconsistent lease */ lcmaps_log(LOG_DEBUG, "%s: will remove existing hard-link to %s and relink to " "%s for lease \"%s\"\n", logstr, otherlink ? otherlink : "unknown", req_mapping, encodedidp); remove_lease=1; } else { /* lease link count is not 2, it's either 0 or 1 */ /* verify mode: error */ if ( (options & ONLY_USE_EXISTING_LEASE)!= 0 ) { lcmaps_log(LOG_NOTICE, "%s: neither requested mapping %s not lease \"%s\" are " "hard-linked\n", logstr, req_mapping, encodedidp); retval=0; goto cleanup; } /* when lease count is 1 we need to remove the solitary lease */ if (st_lease.st_nlink==1) { lcmaps_log(LOG_NOTICE, "%s: removing solitary lease \"%s\"\n", logstr, encodedidp); remove_lease=1; } } /* remove old lease when necessary */ if (remove_lease && unlink(encodedfilename)<0) { lcmaps_log(LOG_ERR, "%s: cannot unlink \"%s\": %s\n", logstr, encodedfilename, strerror(errno)); goto cleanup; } /* create new lease */ if (create_link(req_mapping, mappingfilename, st_mapping.st_ino, encodedidp, encodedfilename)!=1) goto cleanup; /* success */ lcmaps_log(LOG_DEBUG, "%s: lease \"%s\" successfully mapped to requested pool-entry \"%s\"\n", logstr, encodedidp, req_mapping); retval=1; cleanup: free(encodedfilename); free(mappingfilename); free(otherlink); return retval; } /** * Tries to find a pool mapping in the (grid)mapdir for given encoded(globus)idp * and pool-name. * When options contains ONLY_USE_EXISTING_LEASE no new lease will be created. * When options contains OVERRIDE_INCONSISTANCY an existing hardlink will be * replaced. Upon success the resulting pool-entry is returned in mapping, which * needs to be freed. * return 1 on succes, 0 on no mapping, -1 on error */ static int get_pool_mapping(const char *mapdir, const char *encodedidp, const char *poolname, int options, char **mapping) { const char *logstr=LOGSTR_PREFIX"get_pool_mapping"; int remove_lease=0, rc, retval=-1; /* default: error */ char *encodedfilename=NULL, *mappingfilename=NULL, *otherlink=NULL, *name; struct stat st_lease, st_mapping; DIR *dir=NULL; struct dirent *direntry; /* Check input consistency */ if ( !mapdir || !encodedidp || !poolname || poolname[0]=='\0' || !mapping ) return -1; /* get absolute filename for lease */ if (get_fullname(mapdir, encodedidp, &encodedfilename)<0) return -1; /* get stat info for lease: may fail with a ENOENT */ if (lstat(encodedfilename, &st_lease)<0) { if (errno!=ENOENT) { lcmaps_log(LOG_ERR, "%s: cannot stat lease \"%s\": %s\n", logstr, encodedfilename, strerror(errno)); goto cleanup; } /* lease does not exist: that's fine: go to dir loop */ } else { /* existing lease: sanity check: regular file */ if (!S_ISREG(st_lease.st_mode)) { lcmaps_log(LOG_WARNING, "%s: lease is not a regular file: \"%s\"\n", logstr, encodedidp); goto cleanup; } /* First handle different cases depending on num of links for lease */ switch (st_lease.st_nlink) { case 1: /* solitary lease: remove, then create new lease */ lcmaps_log(LOG_NOTICE,"%s: will remove solitary lease \"%s\"\n", logstr, encodedidp); remove_lease=1; break; case 2: /* lease is already hardlinked: get link name to match it */ rc=get_otherlink(mapdir,encodedidp,st_lease.st_ino,&otherlink); if (rc<0) /* error */ goto cleanup; if (rc==0) /* link has disappeared: create new lease */ break; if (rc==1) { /* target gone or not in mapdir, now solitary lease: * remove, then create new lease */ lcmaps_log(LOG_NOTICE, "%s: no other link in %s, will remove solitary " "lease \"%s\"\n", logstr, mapdir, encodedidp); remove_lease=1; break; } /* rc==2: found otherlink: check it's valid */ rc=pool_match(otherlink, poolname, options); if (rc<0) /* error */ goto cleanup; if (rc==1) { /* valid pool account */ /* success: we found valid existing mapping */ lcmaps_log(LOG_DEBUG, "%s: reusing existing lease \"%s\" to %s\n", logstr, encodedidp, otherlink); /* touch existing lease */ if (utime(encodedfilename, NULL)<0) { lcmaps_log(LOG_NOTICE, "%s: touching requested lease \"%s\" failed: %s\n", logstr, encodedfilename, strerror(errno)); free(otherlink); goto cleanup; } /* Set mapping and leave */ *mapping=otherlink; retval=1; goto cleanup; } /* wrong pool: either remove or fail. */ /* Note: we can't be in verify mode (i.e. use existing lease) */ if ( (options & OVERRIDE_INCONSISTANCY) == 0 ) { /* stuck with inconsistent lease */ lcmaps_log (LOG_NOTICE, "%s: wrong pool-entry %s for pool %s and hard link " "named \"%s\"\n", logstr,otherlink,poolname,encodedidp); free(otherlink); retval=0; goto cleanup; } /* We should remove old one */ lcmaps_log(LOG_INFO, "%s: will remove inconsistent lease \"%s\" to %s\n", logstr, encodedidp, otherlink); free(otherlink); remove_lease=1; break; default: /* more than 2 links to lease */ lcmaps_log(LOG_ERR, "%s: too many links (=%lu) to \"%s\"\n", logstr, (unsigned long)(st_lease.st_nlink), encodedidp); goto cleanup; } /* end of switch (st_lease.st_nlink) */ } /* end of else: lstat() succeeded */ /* Need to create a new lease */ /* open gridmapdir */ if ((dir = opendir(mapdir)) == NULL) { lcmaps_log(LOG_ERR, "%s: error opening directory %s: %s\n", logstr, mapdir, strerror(errno)); goto cleanup; } /* Loop over entries in dir to find a valid mapping */ while ((direntry = readdir(dir)) != NULL) { name=direntry->d_name; /* skip unsuitable entries */ if (name[0] == '%' || /* encoded DN/FQAN */ name[0] == '.' || /* hidden file or directory */ strcmp(name, "root") == 0 || /* root account */ strchr(name, '~') != NULL || /* backup file */ pool_match(name, poolname, options)!=1) /* wrong pool (or error) */ continue; /* get absolute filename for mapping */ if (get_fullname(mapdir, name, &mappingfilename)<0) goto cleanup; /* stat the new file */ if (lstat(mappingfilename, &st_mapping)<0) { /* error */ lcmaps_log(LOG_ERR, "%s: cannot stat pool-entry %s: %s\n", logstr, mappingfilename, strerror(errno)); goto cleanup; } /* is it free and a valid entry? */ if (st_mapping.st_nlink!=1 || !S_ISREG(st_mapping.st_mode)) { free(mappingfilename); mappingfilename=NULL; continue; /* already taken */ } /* remove old lease (when it exists) */ if (remove_lease && unlink(encodedfilename)<0 && errno!=ENOENT) { lcmaps_log(LOG_ERR, "%s: cannot unlink \"%s\": %s\n", logstr, encodedfilename, strerror(errno)); goto cleanup; } /* create new lease */ if (create_link(name, mappingfilename, st_mapping.st_ino, encodedidp, encodedfilename)!=1) goto cleanup; /* copy name */ if ( (*mapping=strdup(name))==NULL ) { lcmaps_log(LOG_ERR, "%s: out of memory\n", logstr); goto cleanup; } /* success */ lcmaps_log(LOG_DEBUG, "%s: lease \"%s\" successfully mapped to pool entry %s\n", logstr, encodedidp, name); retval=1; goto cleanup; } /* No match */ lcmaps_log(LOG_NOTICE, "%s: no pool-entry available for pool-name %s and lease \"%s\"\n", logstr, poolname, encodedidp); retval=0; goto cleanup; cleanup: if (dir) closedir(dir); free(encodedfilename); free(mappingfilename); /* return with correct value */ return retval; } /** * Prefixes name with mapdir. fullname needs to be freed. * return 0 on success, -1 on error */ static int get_fullname(const char *mapdir, const char *name, char **fullname) { const char *logstr=LOGSTR_PREFIX"get_fullname"; int len; char *filename=NULL; /* Sanity check */ if (!mapdir || !name || !fullname) return -1; /* Create full link name */ if ( (len=snprintf(filename, 0, "%s/%s", mapdir, name)) < 0) { lcmaps_log(LOG_ERR,"%s: snprintf failed: %s\n", logstr, strerror(errno)); return -1; } if ( (filename=malloc((size_t)(len+1)))==NULL ) { lcmaps_log(LOG_ERR,"%s: out of memory\n", logstr); return -1; } snprintf(filename, (size_t)(len+1), "%s/%s", mapdir, name); *fullname=filename; return 0; } /** * Finds other hardlink to given firstlink, which is at given inode. Note: this * is a relatively expensive operation as we cycle through the gridmapdir. * return 2 when found, 1 when only firstlink was found, 0 when neither is * found, -1 on error */ static int get_otherlink(const char *mapdir, const char *firstlink, ino_t firstinode, char **otherlink) { const char *logstr=LOGSTR_PREFIX"get_otherlink"; int found_first=0, rc=-1; /* default rc */ char *otherlinkpath=NULL, *otherlinkdup; struct stat statbuf; DIR *dir=NULL; struct dirent *direntry; /* Sanity check */ if (!mapdir || !firstlink || !otherlink) return -1; /* open gridmapdir */ if ((dir = opendir(mapdir)) == NULL) { lcmaps_log(LOG_ERR, "%s: error opening directory %s: %s\n", logstr, mapdir, strerror(errno)); goto cleanup; } /* loop over entries */ while ((direntry = readdir(dir)) != NULL) { if (strcmp(direntry->d_name, firstlink) == 0) { found_first=1; /* found ourselves */ continue; } /* get full name of other link */ if (get_fullname(mapdir, direntry->d_name, &otherlinkpath)<0) goto cleanup; /* get status information */ if (lstat(otherlinkpath, &statbuf)==0 && statbuf.st_ino == firstinode) { /* found other: check link count. */ switch (statbuf.st_nlink) { case 2: /* good */ if ( (otherlinkdup = strdup(direntry->d_name)) == NULL ) { lcmaps_log(LOG_ERR,"%s: out of memory\n", logstr); goto cleanup; } *otherlink=otherlinkdup; rc=2; /* link count 2 */ goto cleanup; case 1: /* first link is removed again */ rc=0; goto cleanup; default: /* odd link count */ lcmaps_log(LOG_NOTICE, "%s: found otherlink %s but linkcount is %lu\n", logstr, direntry->d_name, (unsigned long)(statbuf.st_nlink)); goto cleanup; } } /* Try next */ free(otherlinkpath); otherlinkpath=NULL; } /* we haven't found other, probably disappeared again or we might have a * solitary or out-of-gridmapdir lease. */ rc=found_first; cleanup: if (dir) closedir(dir); free(otherlinkpath); /* return with correct value */ return rc; } /** * Creates a new link or reuses a valid existing link from mapping to lease. * The result should have requested inode req_inode. mapping_filename and * lease_filename should be absolute path names. mapping and lease are the * relative filenames, only used for logging. In case linking failed due to * existing lease which then disappears, we retry (max. RETRY_MAX times) * linking. When link() succeeds and the link is valid, we touch it using * utime() to keep track of the usage. * return 1 on success, 0 on other link went in between, -1 on error */ static int create_link(const char *mapping, const char *mapping_filename, ino_t req_inode, const char *lease, const char *lease_filename) { const char *logstr=LOGSTR_PREFIX"create_link"; struct stat st_lease; int rc_link, try=0; int fd; /* sanity check */ if (!mapping_filename || !mapping || !lease_filename || !lease) return -1; /* check whether we have write access to the source for the hardlink: this * is necessary on older Linux: we can make the hardlink, but cannot touch * it afterwards. Note that opening the file write-only append mode does not * yet update the timestamps. */ if ( (fd=open(mapping_filename, O_WRONLY|O_APPEND)) < 0 ) { lcmaps_log(LOG_ERR, "%s: no write-access to \"%s\": %s\n", logstr, mapping_filename, strerror(errno)); return -1; } /* Successfully opened source for hardlink, now close it again */ close(fd); /* If it fails, we'll try at most RETRY_MAX times. */ while (1) { /* try linking, if it fails due to EEXIST, we'll check the stat */ if ( (rc_link=link(mapping_filename, lease_filename))<0) { if (errno!=EEXIST) { lcmaps_log(LOG_ERR, "%s: cannot link %s to \"%s\": %s\n", logstr, mapping, lease, strerror(errno)); return -1; } } /* lease exists */ if (lstat(lease_filename, &st_lease)<0) { /* stat failed */ if (errno==ENOENT) { /* lease disappeared: try to link again */ if (try++