/** * Look-up accounting object to collect space usage information for user * or group. * * \param env - is the environment passed by the caller * \param dev - is the dt_device storing the accounting object * \param type - is the quota type, either USRQUOTA or GRPQUOTA */ struct dt_object *acct_obj_lookup(const struct lu_env *env, struct dt_device *dev, int type) { struct lquota_thread_info *qti = lquota_info(env); struct dt_object *obj = NULL; ENTRY; lu_local_obj_fid(&qti->qti_fid, qtype2acct_oid(type)); /* lookup the accounting object */ obj = dt_locate(env, dev, &qti->qti_fid); if (IS_ERR(obj)) RETURN(obj); if (!dt_object_exists(obj)) { dt_object_put(env, obj); RETURN(ERR_PTR(-ENOENT)); } if (obj->do_index_ops == NULL) { int rc; /* set up indexing operations */ rc = obj->do_ops->do_index_try(env, obj, &dt_acct_features); if (rc) { CERROR("%s: failed to set up indexing operations for %s" " acct object rc:%d\n", dev->dd_lu_dev.ld_obd->obd_name, qtype_name(type), rc); dt_object_put(env, obj); RETURN(ERR_PTR(rc)); } } RETURN(obj); }
/* * Helper function looking up & creating if not found an index file with a * dynamic fid. */ static struct dt_object * lquota_disk_find_create(const struct lu_env *env, struct dt_device *dev, struct dt_object *parent, struct lu_fid *fid, const struct dt_index_features *idx_feat, char *name) { struct lquota_thread_info *qti = lquota_info(env); struct dt_object *obj; struct local_oid_storage *los; int rc; ENTRY; /* Set up local storage */ rc = local_oid_storage_init(env, dev, fid, &los); if (rc) RETURN(ERR_PTR(rc)); /* lookup/create slave index file */ obj = local_index_find_or_create(env, los, parent, name, LQUOTA_MODE, idx_feat); if (IS_ERR(obj)) GOTO(out, obj); /* local_oid_storage_fini() will finalize the local storage device, * we have to open the object in another device stack */ qti->qti_fid = obj->do_lu.lo_header->loh_fid; lu_object_put_nocache(env, &obj->do_lu); obj = dt_locate(env, dev, &qti->qti_fid); if (IS_ERR(obj)) GOTO(out, obj); out: local_oid_storage_fini(env, los); RETURN(obj); }
/* * Reserve enough credits to update a record in a quota index file. * * \param env - is the environment passed by the caller * \param th - is the transaction to use for disk writes * \param obj - is the on-disk index where quota settings are stored. * \param id - is the key to be updated * * \retval - 0 on success, appropriate error on failure */ int lquota_disk_declare_write(const struct lu_env *env, struct thandle *th, struct dt_object *obj, union lquota_id *id) { struct lquota_thread_info *qti = lquota_info(env); struct dt_key *key = (struct dt_key *)&id->qid_uid; int rc; ENTRY; LASSERT(dt_object_exists(obj)); LASSERT(obj->do_index_ops != NULL); /* speculative delete declaration in case there is already an existing * record in the index */ rc = dt_declare_delete(env, obj, key, th); if (rc) RETURN(rc); /* declare insertion of updated record */ rc = dt_declare_insert(env, obj, (struct dt_rec *)&qti->qti_rec, key, th); if (rc) RETURN(rc); /* we might have to update the version of the global index too */ rc = dt_declare_version_set(env, obj, th); RETURN(rc); }
/* * Look-up/create a global index file. * * \param env - is the environment passed by the caller * \parap dev - is the dt_device where to lookup/create the global index file * \param parent - is the parent directory where to create the global index if * not found * \param fid - is the fid of the global index to be looked up/created * \parap local - indicates whether the index should be created with a local * generated fid or with \fid * * \retval - pointer to the dt_object of the global index on success, * appropriate error on failure */ struct dt_object *lquota_disk_glb_find_create(const struct lu_env *env, struct dt_device *dev, struct dt_object *parent, struct lu_fid *fid, bool local) { struct lquota_thread_info *qti = lquota_info(env); struct dt_object *glb_idx; const struct dt_index_features *idx_feat; ENTRY; CDEBUG(D_QUOTA, "look-up/create %sglobal idx file ("DFID")\n", local ? "local " : "", PFID(fid)); idx_feat = &dt_quota_glb_features; /* the filename is composed of the most signicant bits of the FID, * that's to say the oid which encodes the pool id, pool type and quota * type */ sprintf(qti->qti_buf, "0x%x", fid->f_oid); if (local) { /* We use the sequence reserved for local named objects */ lu_local_name_obj_fid(&qti->qti_fid, 1); glb_idx = lquota_disk_find_create(env, dev, parent, &qti->qti_fid, idx_feat, qti->qti_buf); } else { /* look-up/create global index on disk */ glb_idx = local_index_find_or_create_with_fid(env, dev, fid, parent, qti->qti_buf, LQUOTA_MODE, idx_feat); } if (IS_ERR(glb_idx)) { CERROR("%s: failed to look-up/create idx file "DFID" rc:%ld " "local:%d\n", dev->dd_lu_dev.ld_obd->obd_name, PFID(fid), PTR_ERR(glb_idx), local); RETURN(glb_idx); } /* install index operation vector */ if (glb_idx->do_index_ops == NULL) { int rc; rc = glb_idx->do_ops->do_index_try(env, glb_idx, idx_feat); if (rc) { CERROR("%s: failed to setup index operations for "DFID " rc:%d\n", dev->dd_lu_dev.ld_obd->obd_name, PFID(lu_object_fid(&glb_idx->do_lu)), rc); dt_object_put(env, glb_idx); glb_idx = ERR_PTR(rc); } } RETURN(glb_idx); }
/* * Look-up a slave index file. * * \param env - is the environment passed by the caller * \param dev - is the backend dt_device where to look-up/create the slave index * \param parent - is the parent directory where to lookup the slave index * \param glb_fid - is the fid of the global index file associated with this * slave index. * \param uuid - is the uuid of slave which is (re)connecting to the master * target * * \retval - pointer to the dt_object of the slave index on success, * appropriate error on failure */ struct dt_object *lquota_disk_slv_find(const struct lu_env *env, struct dt_device *dev, struct dt_object *parent, const struct lu_fid *glb_fid, struct obd_uuid *uuid) { struct lquota_thread_info *qti = lquota_info(env); struct dt_object *slv_idx; int rc; ENTRY; LASSERT(uuid != NULL); CDEBUG(D_QUOTA, "lookup slave index file for %s\n", obd_uuid2str(uuid)); /* generate filename associated with the slave */ rc = lquota_disk_slv_filename(glb_fid, uuid, qti->qti_buf); if (rc) RETURN(ERR_PTR(rc)); /* lookup slave index file */ rc = dt_lookup_dir(env, parent, qti->qti_buf, &qti->qti_fid); if (rc) RETURN(ERR_PTR(rc)); /* name is found, get the object */ slv_idx = dt_locate(env, dev, &qti->qti_fid); if (IS_ERR(slv_idx)) RETURN(slv_idx); if (slv_idx->do_index_ops == NULL) { rc = slv_idx->do_ops->do_index_try(env, slv_idx, &dt_quota_slv_features); if (rc) { CERROR("%s: failed to setup slave index operations for " "%s, rc:%d\n", dev->dd_lu_dev.ld_obd->obd_name, obd_uuid2str(uuid), rc); lu_object_put(env, &slv_idx->do_lu); slv_idx = ERR_PTR(rc); } } RETURN(slv_idx); }
/** * Initialize slave index object to collect local quota limit for user or group. * * \param env - is the environment passed by the caller * \param dev - is the dt_device storing the slave index object * \param type - is the quota type, either USRQUOTA or GRPQUOTA */ static struct dt_object *quota_obj_lookup(const struct lu_env *env, struct dt_device *dev, int type) { struct lquota_thread_info *qti = lquota_info(env); struct dt_object *obj = NULL; ENTRY; qti->qti_fid.f_seq = FID_SEQ_QUOTA; qti->qti_fid.f_oid = qtype2slv_oid(type); qti->qti_fid.f_ver = 0; /* lookup the quota object */ obj = dt_locate(env, dev, &qti->qti_fid); if (IS_ERR(obj)) RETURN(obj); if (!dt_object_exists(obj)) { dt_object_put(env, obj); RETURN(ERR_PTR(-ENOENT)); } if (obj->do_index_ops == NULL) { int rc; /* set up indexing operations */ rc = obj->do_ops->do_index_try(env, obj, &dt_quota_slv_features); if (rc) { CERROR("%s: failed to set up indexing operations for %s" " slave index object rc:%d\n", dev->dd_lu_dev.ld_obd->obd_name, qtype_name(type), rc); dt_object_put(env, obj); RETURN(ERR_PTR(rc)); } } RETURN(obj); }
/* * Helper routine to retrieve slave information. * This function converts a quotactl request into quota/accounting object * operations. It is independant of the slave stack which is only accessible * from the OSD layer. * * \param env - is the environment passed by the caller * \param dev - is the dt_device this quotactl is executed on * \param oqctl - is the quotactl request */ int lquotactl_slv(const struct lu_env *env, struct dt_device *dev, struct obd_quotactl *oqctl) { struct lquota_thread_info *qti = lquota_info(env); __u64 key; struct dt_object *obj; struct obd_dqblk *dqblk = &oqctl->qc_dqblk; int rc; ENTRY; if (oqctl->qc_cmd != Q_GETOQUOTA) { /* as in many other places, dev->dd_lu_dev.ld_obd->obd_name * point to an invalid obd_name, to be fixed in LU-1574 */ CERROR("%s: Unsupported quotactl command: %x\n", dev->dd_lu_dev.ld_obd->obd_name, oqctl->qc_cmd); RETURN(-EOPNOTSUPP); } if (oqctl->qc_type < 0 || oqctl->qc_type >= LL_MAXQUOTAS) RETURN(-EOPNOTSUPP); /* qc_id is a 32-bit field while a key has 64 bits */ key = oqctl->qc_id; /* Step 1: collect accounting information */ obj = acct_obj_lookup(env, dev, oqctl->qc_type); if (IS_ERR(obj)) RETURN(-EOPNOTSUPP); if (obj->do_index_ops == NULL) GOTO(out, rc = -EINVAL); /* lookup record storing space accounting information for this ID */ rc = dt_lookup(env, obj, (struct dt_rec *)&qti->qti_acct_rec, (struct dt_key *)&key); if (rc < 0) GOTO(out, rc); memset(&oqctl->qc_dqblk, 0, sizeof(struct obd_dqblk)); dqblk->dqb_curspace = qti->qti_acct_rec.bspace; dqblk->dqb_curinodes = qti->qti_acct_rec.ispace; dqblk->dqb_valid = QIF_USAGE; dt_object_put(env, obj); /* Step 2: collect enforcement information */ obj = quota_obj_lookup(env, dev, oqctl->qc_type); if (IS_ERR(obj)) RETURN(0); if (obj->do_index_ops == NULL) GOTO(out, rc = 0); memset(&qti->qti_slv_rec, 0, sizeof(qti->qti_slv_rec)); /* lookup record storing enforcement information for this ID */ rc = dt_lookup(env, obj, (struct dt_rec *)&qti->qti_slv_rec, (struct dt_key *)&key); if (rc < 0 && rc != -ENOENT) GOTO(out, rc = 0); if (lu_device_is_md(dev->dd_lu_dev.ld_site->ls_top_dev)) { dqblk->dqb_ihardlimit = qti->qti_slv_rec.qsr_granted; dqblk->dqb_bhardlimit = 0; } else { dqblk->dqb_ihardlimit = 0; dqblk->dqb_bhardlimit = qti->qti_slv_rec.qsr_granted; } dqblk->dqb_valid |= QIF_LIMITS; GOTO(out, rc = 0); out: dt_object_put(env, obj); return rc; }
/* * Update a record in a quota index file. * * \param env - is the environment passed by the caller * \param th - is the transaction to use for disk writes * \param obj - is the on-disk index to be updated. * \param id - is the key to be updated * \param rec - is the input record containing the new quota settings. * \param flags - can be LQUOTA_BUMP_VER or LQUOTA_SET_VER. * \param ver - is the new version of the index if LQUOTA_SET_VER is set or is * used to return the new version of the index when * LQUOTA_BUMP_VER is set. * * \retval - 0 on success, appropriate error on failure */ int lquota_disk_write(const struct lu_env *env, struct thandle *th, struct dt_object *obj, union lquota_id *id, struct dt_rec *rec, __u32 flags, __u64 *ver) { struct lquota_thread_info *qti = lquota_info(env); struct dt_key *key = (struct dt_key *)&id->qid_uid; int rc; ENTRY; LASSERT(dt_object_exists(obj)); LASSERT(obj->do_index_ops != NULL); /* lock index */ dt_write_lock(env, obj, 0); /* check whether there is already an existing record for this ID */ rc = dt_lookup(env, obj, (struct dt_rec *)&qti->qti_rec, key, BYPASS_CAPA); if (rc == 0) { /* delete existing record in order to replace it */ rc = dt_delete(env, obj, key, th, BYPASS_CAPA); if (rc) GOTO(out, rc); } else if (rc == -ENOENT) { /* probably first insert */ rc = 0; } else { GOTO(out, rc); } if (rec != NULL) { /* insert record with updated quota settings */ rc = dt_insert(env, obj, rec, key, th, BYPASS_CAPA, 1); if (rc) { /* try to insert the old one */ rc = dt_insert(env, obj, (struct dt_rec *)&qti->qti_rec, key, th, BYPASS_CAPA, 1); LASSERTF(rc == 0, "failed to insert record in quota " "index "DFID, PFID(lu_object_fid(&obj->do_lu))); GOTO(out, rc); } } if (flags != 0) { LASSERT(ver); if (flags & LQUOTA_BUMP_VER) { /* caller wants to bump the version, let's first read * it */ *ver = dt_version_get(env, obj); (*ver)++; } else { LASSERT(flags & LQUOTA_SET_VER); } dt_version_set(env, obj, *ver, th); } EXIT; out: dt_write_unlock(env, obj); return rc; }
/* * Iterate over all slave index files associated with global index \glb_fid and * invoke a callback function for each slave index file. * * \param env - is the environment passed by the caller * \param parent - is the parent directory where the slave index files are * stored * \param glb_fid - is the fid of the global index file associated with the * slave indexes to scan * \param func - is the callback function to call each time a slave index * file is found * \param arg - is an opaq argument passed to the callback function \func */ int lquota_disk_for_each_slv(const struct lu_env *env, struct dt_object *parent, struct lu_fid *glb_fid, lquota_disk_slv_cb_t func, void *arg) { struct lquota_thread_info *qti = lquota_info(env); struct dt_it *it; const struct dt_it_ops *iops; char *name; int rc; ENTRY; OBD_ALLOC(name, LQUOTA_NAME_MAX); if (name == NULL) RETURN(-ENOMEM); /* filename associated with slave index files are prefixed with the most * signicant bits of the global FID */ sprintf(name, "0x%x-", glb_fid->f_oid); iops = &parent->do_index_ops->dio_it; it = iops->init(env, parent, 0, BYPASS_CAPA); if (IS_ERR(it)) { OBD_FREE(name, LQUOTA_NAME_MAX); RETURN(PTR_ERR(it)); } rc = iops->load(env, it, 0); if (rc == 0) { /* * Iterator didn't find record with exactly the key requested. * * It is currently either * * - positioned above record with key less than * requested---skip it. * * - or not positioned at all (is in IAM_IT_SKEWED * state)---position it on the next item. */ rc = iops->next(env, it); } else if (rc > 0) rc = 0; while (rc == 0) { struct dt_key *key; int len; len = iops->key_size(env, it); /* IAM iterator can return record with zero len. */ if (len == 0 || len <= strlen(name) || len >= LQUOTA_NAME_MAX) goto next; key = iops->key(env, it); if (IS_ERR(key)) { rc = PTR_ERR(key); break; } if (strncmp((char *)key, name, strlen(name)) != 0) goto next; /* ldiskfs OSD returns filename as stored in directory entry * which does not end up with '\0' */ memcpy(&qti->qti_buf, key, len); qti->qti_buf[len] = '\0'; /* lookup fid associated with this slave index file */ rc = dt_lookup_dir(env, parent, qti->qti_buf, &qti->qti_fid); if (rc) break; if (qti->qti_fid.f_seq != FID_SEQ_QUOTA) goto next; rc = func(env, glb_fid, (char *)key, &qti->qti_fid, arg); if (rc) break; next: do { rc = iops->next(env, it); } while (rc == -ESTALE); } iops->put(env, it); iops->fini(env, it); OBD_FREE(name, LQUOTA_NAME_MAX); if (rc > 0) rc = 0; RETURN(rc); }
/* * Look-up a slave index file. If the slave index isn't found: * - if local is set to false, we allocate a FID from FID_SEQ_QUOTA sequence and * create the index. * - otherwise, we create the index file with a local reserved FID (see * lquota_local_oid) * * \param env - is the environment passed by the caller * \param dev - is the backend dt_device where to look-up/create the slave index * \param parent - is the parent directory where to create the slave index if * it does not exist already * \param glb_fid - is the fid of the global index file associated with this * slave index. * \param uuid - is the uuid of slave which is (re)connecting to the master * target * \param local - indicate whether to use local reserved FID (LQUOTA_USR_OID * & LQUOTA_GRP_OID) for the slave index creation or to * allocate a new fid from sequence FID_SEQ_QUOTA * * \retval - pointer to the dt_object of the slave index on success, * appropriate error on failure */ struct dt_object *lquota_disk_slv_find_create(const struct lu_env *env, struct dt_device *dev, struct dt_object *parent, struct lu_fid *glb_fid, struct obd_uuid *uuid, bool local) { struct lquota_thread_info *qti = lquota_info(env); struct dt_object *slv_idx; int rc; ENTRY; LASSERT(uuid != NULL); CDEBUG(D_QUOTA, "lookup/create slave index file for %s\n", obd_uuid2str(uuid)); /* generate filename associated with the slave */ rc = lquota_disk_slv_filename(glb_fid, uuid, qti->qti_buf); if (rc) RETURN(ERR_PTR(rc)); /* Slave indexes uses the FID_SEQ_QUOTA sequence since they can be read * through the network */ qti->qti_fid.f_seq = FID_SEQ_QUOTA; qti->qti_fid.f_ver = 0; if (local) { int type; rc = lquota_extract_fid(glb_fid, NULL, NULL, &type); if (rc) RETURN(ERR_PTR(rc)); /* use predefined fid in the reserved oid list */ qti->qti_fid.f_oid = (type == USRQUOTA) ? LQUOTA_USR_OID : LQUOTA_GRP_OID; slv_idx = local_index_find_or_create_with_fid(env, dev, &qti->qti_fid, parent, qti->qti_buf, LQUOTA_MODE, &dt_quota_slv_features); } else { /* allocate fid dynamically if index does not exist already */ qti->qti_fid.f_oid = LQUOTA_GENERATED_OID; /* lookup/create slave index file */ slv_idx = lquota_disk_find_create(env, dev, parent, &qti->qti_fid, &dt_quota_slv_features, qti->qti_buf); } if (IS_ERR(slv_idx)) RETURN(slv_idx); /* install index operation vector */ if (slv_idx->do_index_ops == NULL) { rc = slv_idx->do_ops->do_index_try(env, slv_idx, &dt_quota_slv_features); if (rc) { CERROR("%s: failed to setup index operations for "DFID " rc:%d\n", dev->dd_lu_dev.ld_obd->obd_name, PFID(lu_object_fid(&slv_idx->do_lu)), rc); lu_object_put(env, &slv_idx->do_lu); slv_idx = ERR_PTR(rc); } } RETURN(slv_idx); }
/* * Look-up/create a global index file. * * \param env - is the environment passed by the caller * \parap dev - is the dt_device where to lookup/create the global index file * \param parent - is the parent directory where to create the global index if * not found * \param fid - is the fid of the global index to be looked up/created * \parap local - indicates whether the index should be created with a local * generated fid or with \fid * * \retval - pointer to the dt_object of the global index on success, * appropriate error on failure */ struct dt_object *lquota_disk_glb_find_create(const struct lu_env *env, struct dt_device *dev, struct dt_object *parent, struct lu_fid *fid, bool local) { struct lquota_thread_info *qti = lquota_info(env); struct dt_object *glb_idx; const struct dt_index_features *idx_feat; ENTRY; CDEBUG(D_QUOTA, "look-up/create %sglobal idx file ("DFID")\n", local ? "local " : "", PFID(fid)); #if LUSTRE_VERSION_CODE < OBD_OCD_VERSION(2,7,50,0) /* we use different index feature for each quota type and target type * for the time being. This is done for on-disk conversion from the old * quota format. Once this is no longer required, we should just be * using dt_quota_glb_features for all global index file */ idx_feat = glb_idx_feature(fid); #else #warning "remove old quota compatibility code" idx_feat = &dt_quota_glb_features; #endif /* the filename is composed of the most signicant bits of the FID, * that's to say the oid which encodes the pool id, pool type and quota * type */ sprintf(qti->qti_buf, "0x%x", fid->f_oid); if (local) { /* We use the sequence reserved for local named objects */ lu_local_name_obj_fid(&qti->qti_fid, 1); glb_idx = lquota_disk_find_create(env, dev, parent, &qti->qti_fid, idx_feat, qti->qti_buf); } else { /* look-up/create global index on disk */ glb_idx = local_index_find_or_create_with_fid(env, dev, fid, parent, qti->qti_buf, LQUOTA_MODE, idx_feat); } if (IS_ERR(glb_idx)) { CERROR("%s: failed to look-up/create idx file "DFID" rc:%ld " "local:%d\n", dev->dd_lu_dev.ld_obd->obd_name, PFID(fid), PTR_ERR(glb_idx), local); RETURN(glb_idx); } /* install index operation vector */ if (glb_idx->do_index_ops == NULL) { int rc; rc = glb_idx->do_ops->do_index_try(env, glb_idx, idx_feat); if (rc) { CERROR("%s: failed to setup index operations for "DFID " rc:%d\n", dev->dd_lu_dev.ld_obd->obd_name, PFID(lu_object_fid(&glb_idx->do_lu)), rc); lu_object_put(env, &glb_idx->do_lu); glb_idx = ERR_PTR(rc); } } RETURN(glb_idx); }
/* * Set up quota directory (either "quota_master" or "quota_slave") for a QMT or * QSD instance. This function is also used to create per-pool directory on * the quota master. * The directory is created with a local sequence if it does not exist already. * This function is called at ->ldo_prepare time when the full device stack is * configured. * * \param env - is the environment passed by the caller * \param dev - is the dt_device where to create the quota directory * \param parent - is the parent directory. If not specified, the directory * will be created under the root directory * \param name - is the name of quota directory to be created * * \retval - pointer to quota root dt_object on success, appropriate error * on failure */ struct dt_object *lquota_disk_dir_find_create(const struct lu_env *env, struct dt_device *dev, struct dt_object *parent, const char *name) { struct lquota_thread_info *qti = lquota_info(env); struct dt_object *qt_dir = NULL; struct local_oid_storage *los = NULL; int rc; ENTRY; /* Set up local storage to create the quota directory. * We use the sequence reserved for local named objects */ lu_local_name_obj_fid(&qti->qti_fid, 1); rc = local_oid_storage_init(env, dev, &qti->qti_fid, &los); if (rc) RETURN(ERR_PTR(rc)); if (parent == NULL) { /* Fetch dt object associated with root directory */ rc = dt_root_get(env, dev, &qti->qti_fid); if (rc) GOTO(out, rc); parent = dt_locate_at(env, dev, &qti->qti_fid, dev->dd_lu_dev.ld_site->ls_top_dev, NULL); if (IS_ERR(parent)) GOTO(out, rc = PTR_ERR(parent)); } else { lu_object_get(&parent->do_lu); } /* create quota directory to be used for all quota index files */ qt_dir = local_file_find_or_create(env, los, parent, name, S_IFDIR | S_IRUGO | S_IWUSR | S_IXUGO); if (IS_ERR(qt_dir)) GOTO(out, rc = PTR_ERR(qt_dir)); /* local_oid_storage_fini() will finalize the local storage device, * we have to open the object in another device stack */ qti->qti_fid = qt_dir->do_lu.lo_header->loh_fid; lu_object_put_nocache(env, &qt_dir->do_lu); qt_dir = dt_locate(env, dev, &qti->qti_fid); if (IS_ERR(qt_dir)) GOTO(out, rc = PTR_ERR(qt_dir)); if (!dt_try_as_dir(env, qt_dir)) GOTO(out, rc = -ENOTDIR); EXIT; out: if (parent != NULL && !IS_ERR(parent)) lu_object_put(env, &parent->do_lu); if (los != NULL) local_oid_storage_fini(env, los); if (rc) { if (qt_dir != NULL && !IS_ERR(qt_dir)) lu_object_put(env, &qt_dir->do_lu); qt_dir = ERR_PTR(rc); } return qt_dir; }