/src/netcdf-c/libsrc4/nc4grp.c
| Line | Count | Source | 
| 1 |  | /* Copyright 2005-2018, University Corporation for Atmospheric | 
| 2 |  |  * Research. See COPYRIGHT file for copying and redistribution | 
| 3 |  |  * conditions. */ | 
| 4 |  | /** | 
| 5 |  |  * @file | 
| 6 |  |  * @internal This file is part of netcdf-4, a netCDF-like interface | 
| 7 |  |  * for HDF5, or a HDF5 backend for netCDF, depending on your point of | 
| 8 |  |  * view. | 
| 9 |  |  * | 
| 10 |  |  * This file handles groups. | 
| 11 |  |  * | 
| 12 |  |  * @author Ed Hartnett | 
| 13 |  |  */ | 
| 14 |  |  | 
| 15 |  | #include "nc4internal.h" | 
| 16 |  | #include "nc4dispatch.h" | 
| 17 |  | /** | 
| 18 |  |  * @internal Given an ncid and group name (NULL gets root group), | 
| 19 |  |  * return the ncid of that group. | 
| 20 |  |  * | 
| 21 |  |  * @param ncid File and group ID. | 
| 22 |  |  * @param name Pointer that gets name. | 
| 23 |  |  * @param grp_ncid Pointer that gets group ncid. | 
| 24 |  |  * | 
| 25 |  |  * @return ::NC_NOERR No error. | 
| 26 |  |  * @return ::NC_EBADID Bad ncid. | 
| 27 |  |  * @return ::NC_ENOTNC4 Not a netCDF-4 file. | 
| 28 |  |  * @return ::NC_ENOGRP Group not found. | 
| 29 |  |  * @author Ed Hartnett | 
| 30 |  |  */ | 
| 31 |  | int | 
| 32 |  | NC4_inq_ncid(int ncid, const char *name, int *grp_ncid) | 
| 33 | 0 | { | 
| 34 | 0 |     NC_GRP_INFO_T *grp, *g; | 
| 35 | 0 |     NC_FILE_INFO_T *h5; | 
| 36 | 0 |     char norm_name[NC_MAX_NAME + 1]; | 
| 37 | 0 |     int retval; | 
| 38 |  | 
 | 
| 39 | 0 |     LOG((2, "nc_inq_ncid: ncid 0x%x name %s", ncid, name)); | 
| 40 |  |  | 
| 41 |  |     /* Find info for this file and group, and set pointer to each. */ | 
| 42 | 0 |     if ((retval = nc4_find_grp_h5(ncid, &grp, &h5))) | 
| 43 | 0 |         return retval; | 
| 44 | 0 |     assert(h5); | 
| 45 |  |  | 
| 46 |  |     /* Short circuit the case of name == NULL => return the root group */ | 
| 47 | 0 |     if(name == NULL) { | 
| 48 | 0 |   if(grp_ncid) { | 
| 49 | 0 |       NC_FILE_INFO_T* file = grp->nc4_info; | 
| 50 | 0 |             *grp_ncid = file->controller->ext_ncid | file->root_grp->hdr.id; | 
| 51 | 0 |   }   | 
| 52 | 0 |   return NC_NOERR; | 
| 53 | 0 |     } | 
| 54 |  |  | 
| 55 |  |     /* Normalize name. */ | 
| 56 | 0 |     if ((retval = nc4_check_name(name, norm_name))) | 
| 57 | 0 |         return retval; | 
| 58 |  |  | 
| 59 | 0 |     g = (NC_GRP_INFO_T*)ncindexlookup(grp->children,norm_name); | 
| 60 | 0 |     if(g != NULL) | 
| 61 | 0 |     { | 
| 62 | 0 |         if (grp_ncid) | 
| 63 | 0 |             *grp_ncid = grp->nc4_info->controller->ext_ncid | g->hdr.id; | 
| 64 | 0 |         return NC_NOERR; | 
| 65 | 0 |     } | 
| 66 |  |  | 
| 67 |  |     /* If we got here, we didn't find the named group. */ | 
| 68 | 0 |     return NC_ENOGRP; | 
| 69 | 0 | } | 
| 70 |  |  | 
| 71 |  | /** | 
| 72 |  |  * @internal Given a location id, return the number of groups it | 
| 73 |  |  * contains, and an array of their locids. | 
| 74 |  |  * | 
| 75 |  |  * @param ncid File and group ID. | 
| 76 |  |  * @param numgrps Pointer that gets number of groups. Ignored if NULL. | 
| 77 |  |  * @param ncids Pointer that gets array of ncids. Ignored if NULL. | 
| 78 |  |  * | 
| 79 |  |  * @return ::NC_NOERR No error. | 
| 80 |  |  * @return ::NC_EBADID Bad ncid. | 
| 81 |  |  * @author Ed Hartnett | 
| 82 |  |  */ | 
| 83 |  | int | 
| 84 |  | NC4_inq_grps(int ncid, int *numgrps, int *ncids) | 
| 85 | 0 | { | 
| 86 | 0 |     NC_GRP_INFO_T *grp, *g; | 
| 87 | 0 |     NC_FILE_INFO_T *h5; | 
| 88 | 0 |     int num = 0; | 
| 89 | 0 |     int retval; | 
| 90 |  | 
 | 
| 91 | 0 |     LOG((2, "nc_inq_grps: ncid 0x%x", ncid)); | 
| 92 |  |  | 
| 93 |  |     /* Find info for this file and group, and set pointer to each. */ | 
| 94 | 0 |     if ((retval = nc4_find_grp_h5(ncid, &grp, &h5))) | 
| 95 | 0 |         return retval; | 
| 96 | 0 |     assert(h5); | 
| 97 |  |  | 
| 98 |  |     /* Count the number of groups in this group. */ | 
| 99 | 0 |     for(size_t i=0;i<ncindexsize(grp->children);i++) | 
| 100 | 0 |     { | 
| 101 | 0 |         g = (NC_GRP_INFO_T*)ncindexith(grp->children,i); | 
| 102 | 0 |         if(g == NULL) continue; | 
| 103 | 0 |         if (ncids) | 
| 104 | 0 |         { | 
| 105 |  |             /* Combine the nc_grpid in a bitwise or with the ext_ncid, | 
| 106 |  |              * which allows the returned ncid to carry both file and | 
| 107 |  |              * group information. */ | 
| 108 | 0 |             *ncids = g->hdr.id | g->nc4_info->controller->ext_ncid; | 
| 109 | 0 |             ncids++; | 
| 110 | 0 |         } | 
| 111 | 0 |         num++; | 
| 112 | 0 |     } | 
| 113 |  | 
 | 
| 114 | 0 |     if (numgrps) | 
| 115 | 0 |         *numgrps = num; | 
| 116 |  | 
 | 
| 117 | 0 |     return NC_NOERR; | 
| 118 | 0 | } | 
| 119 |  |  | 
| 120 |  | /** | 
| 121 |  |  * @internal Given locid, find name of group. (Root group is named | 
| 122 |  |  * "/".) | 
| 123 |  |  * | 
| 124 |  |  * @param ncid File and group ID. | 
| 125 |  |  * @param name Pointer that gets name. | 
| 126 |  |  | 
| 127 |  |  * @return ::NC_NOERR No error. | 
| 128 |  |  * @return ::NC_EBADID Bad ncid. | 
| 129 |  |  * @author Ed Hartnett | 
| 130 |  |  */ | 
| 131 |  | int | 
| 132 |  | NC4_inq_grpname(int ncid, char *name) | 
| 133 | 0 | { | 
| 134 | 0 |     NC_GRP_INFO_T *grp; | 
| 135 | 0 |     NC_FILE_INFO_T *h5; | 
| 136 | 0 |     int retval; | 
| 137 |  | 
 | 
| 138 | 0 |     LOG((2, "nc_inq_grpname: ncid 0x%x", ncid)); | 
| 139 |  |  | 
| 140 |  |     /* Find info for this file and group, and set pointer to each. */ | 
| 141 | 0 |     if ((retval = nc4_find_grp_h5(ncid, &grp, &h5))) | 
| 142 | 0 |         return retval; | 
| 143 | 0 |     assert(h5); | 
| 144 |  |  | 
| 145 |  |     /* Copy the name. */ | 
| 146 | 0 |     if (name) | 
| 147 | 0 |         strcpy(name, grp->hdr.name); | 
| 148 |  | 
 | 
| 149 | 0 |     return NC_NOERR; | 
| 150 | 0 | } | 
| 151 |  |  | 
| 152 |  | /** | 
| 153 |  |  * @internal Find the full path name to the group represented by | 
| 154 |  |  * ncid. Either pointer argument may be NULL; pass a NULL for the | 
| 155 |  |  * third parameter to get the length of the full path name. The length | 
| 156 |  |  * will not include room for a null pointer. | 
| 157 |  |  * | 
| 158 |  |  * @param ncid File and group ID. | 
| 159 |  |  * @param lenp Pointer that gets length of full name. | 
| 160 |  |  * @param full_name Pointer that gets name. | 
| 161 |  |  * | 
| 162 |  |  * @return ::NC_NOERR No error. | 
| 163 |  |  * @return ::NC_EBADID Bad ncid. | 
| 164 |  |  * @return ::NC_ENOMEM Out of memory. | 
| 165 |  |  * @author Ed Hartnett | 
| 166 |  |  */ | 
| 167 |  | int | 
| 168 |  | NC4_inq_grpname_full(int ncid, size_t *lenp, char *full_name) | 
| 169 | 0 | { | 
| 170 | 0 |     char *name, grp_name[NC_MAX_NAME + 1]; | 
| 171 | 0 |     int g, id = ncid, parent_id, *gid; | 
| 172 | 0 |     int i, ret = NC_NOERR; | 
| 173 |  |  | 
| 174 |  |     /* How many generations? */ | 
| 175 | 0 |     for (g = 0; !NC4_inq_grp_parent(id, &parent_id); g++, id = parent_id) | 
| 176 | 0 |         ; | 
| 177 |  |  | 
| 178 |  |     /* Allocate storage. */ | 
| 179 | 0 |     if (!(name = malloc((size_t)(g + 1) * (NC_MAX_NAME + 1) + 1))) | 
| 180 | 0 |         return NC_ENOMEM; | 
| 181 | 0 |     if (!(gid = malloc((size_t)(g + 1) * sizeof(int)))) | 
| 182 | 0 |     { | 
| 183 | 0 |         free(name); | 
| 184 | 0 |         return NC_ENOMEM; | 
| 185 | 0 |     } | 
| 186 | 0 |     assert(name && gid); | 
| 187 |  |  | 
| 188 |  |     /* Always start with a "/" for the root group. */ | 
| 189 | 0 |     strcpy(name, NC_GROUP_NAME); | 
| 190 |  |  | 
| 191 |  |     /* Get the ncids for all generations. */ | 
| 192 | 0 |     gid[0] = ncid; | 
| 193 | 0 |     for (i = 1; i < g && !ret; i++) | 
| 194 | 0 |         ret = NC4_inq_grp_parent(gid[i - 1], &gid[i]); | 
| 195 |  |  | 
| 196 |  |     /* Assemble the full name. */ | 
| 197 | 0 |     for (i = g - 1; !ret && i >= 0 && !ret; i--) | 
| 198 | 0 |     { | 
| 199 | 0 |         if ((ret = NC4_inq_grpname(gid[i], grp_name))) | 
| 200 | 0 |             break; | 
| 201 | 0 |         strcat(name, grp_name); | 
| 202 | 0 |         if (i) | 
| 203 | 0 |             strcat(name, "/"); | 
| 204 | 0 |     } | 
| 205 |  |  | 
| 206 |  |     /* Give the user the length of the name, if he wants it. */ | 
| 207 | 0 |     if (!ret && lenp) | 
| 208 | 0 |         *lenp = strlen(name); | 
| 209 |  |  | 
| 210 |  |     /* Give the user the name, if he wants it. */ | 
| 211 | 0 |     if (!ret && full_name) | 
| 212 | 0 |         strcpy(full_name, name); | 
| 213 |  | 
 | 
| 214 | 0 |     free(gid); | 
| 215 | 0 |     free(name); | 
| 216 |  | 
 | 
| 217 | 0 |     return ret; | 
| 218 | 0 | } | 
| 219 |  |  | 
| 220 |  | /** | 
| 221 |  |  * @internal Find the parent ncid of a group. For the root group, | 
| 222 |  |  * return NC_ENOGRP error.  *Now* I know what kind of tinfoil hat | 
| 223 |  |  * wearing nut job would call this function with a NULL pointer for | 
| 224 |  |  * parent_ncid - Russ Rew!! | 
| 225 |  |  * | 
| 226 |  |  * @param ncid File and group ID. | 
| 227 |  |  * @param parent_ncid Pointer that gets the ncid of parent group. | 
| 228 |  |  * | 
| 229 |  |  * @return ::NC_NOERR No error. | 
| 230 |  |  * @return ::NC_EBADID Bad ncid. | 
| 231 |  |  * @return ::NC_ENOGRP Root has no parent. | 
| 232 |  |  * @author Ed Hartnett | 
| 233 |  |  */ | 
| 234 |  | int | 
| 235 |  | NC4_inq_grp_parent(int ncid, int *parent_ncid) | 
| 236 | 0 | { | 
| 237 | 0 |     NC_GRP_INFO_T *grp; | 
| 238 | 0 |     NC_FILE_INFO_T *h5; | 
| 239 | 0 |     int retval; | 
| 240 |  | 
 | 
| 241 | 0 |     LOG((2, "nc_inq_grp_parent: ncid 0x%x", ncid)); | 
| 242 |  |  | 
| 243 |  |     /* Find info for this file and group. */ | 
| 244 | 0 |     if ((retval = nc4_find_grp_h5(ncid, &grp, &h5))) | 
| 245 | 0 |         return retval; | 
| 246 | 0 |     assert(h5); | 
| 247 |  |  | 
| 248 |  |     /* Set the parent ncid, if there is one. */ | 
| 249 | 0 |     if (grp->parent) | 
| 250 | 0 |     { | 
| 251 | 0 |         if (parent_ncid) | 
| 252 | 0 |             *parent_ncid = grp->nc4_info->controller->ext_ncid | grp->parent->hdr.id; | 
| 253 | 0 |     } | 
| 254 | 0 |     else | 
| 255 | 0 |         return NC_ENOGRP; | 
| 256 |  |  | 
| 257 | 0 |     return NC_NOERR; | 
| 258 | 0 | } | 
| 259 |  |  | 
| 260 |  | /** | 
| 261 |  |  * @internal Given a full name and ncid, find group ncid. | 
| 262 |  |  * | 
| 263 |  |  * @param ncid File and group ID. | 
| 264 |  |  * @param full_name Full name of group. | 
| 265 |  |  * @param grp_ncid Pointer that gets ncid of group. | 
| 266 |  |  * | 
| 267 |  |  * @return ::NC_NOERR No error. | 
| 268 |  |  * @return ::NC_EBADID Bad ncid. | 
| 269 |  |  * @return ::NC_ENOGRP Group not found. | 
| 270 |  |  * @return ::NC_ENOMEM Out of memory. | 
| 271 |  |  * @return ::NC_EINVAL Name is required. | 
| 272 |  |  * @author Ed Hartnett | 
| 273 |  |  */ | 
| 274 |  | int | 
| 275 |  | NC4_inq_grp_full_ncid(int ncid, const char *full_name, int *grp_ncid) | 
| 276 | 0 | { | 
| 277 | 0 |     NC_GRP_INFO_T *grp; | 
| 278 | 0 |     NC_FILE_INFO_T *h5; | 
| 279 | 0 |     int id1 = ncid, id2; | 
| 280 | 0 |     char *cp, *full_name_cpy; | 
| 281 | 0 |     int ret; | 
| 282 |  | 
 | 
| 283 | 0 |     if (!full_name) | 
| 284 | 0 |         return NC_EINVAL; | 
| 285 |  |  | 
| 286 |  |     /* Find info for this file and group, and set pointer to each. */ | 
| 287 | 0 |     if ((ret = nc4_find_grp_h5(ncid, &grp, &h5))) | 
| 288 | 0 |         return ret; | 
| 289 | 0 |     assert(h5); | 
| 290 |  |  | 
| 291 |  |     /* Copy full_name because strtok messes with the value it works | 
| 292 |  |      * with, and we don't want to mess up full_name. */ | 
| 293 | 0 |     if (!(full_name_cpy = strdup(full_name))) | 
| 294 | 0 |         return NC_ENOMEM; | 
| 295 |  |  | 
| 296 |  |     /* Get the first part of the name. */ | 
| 297 | 0 |     if (!(cp = strtok(full_name_cpy, "/"))) | 
| 298 | 0 |     { | 
| 299 |  |         /* If "/" is passed, and this is the root group, return the root | 
| 300 |  |          * group id. */ | 
| 301 | 0 |         if (!grp->parent) | 
| 302 | 0 |             id2 = ncid; | 
| 303 | 0 |         else | 
| 304 | 0 |         { | 
| 305 | 0 |             free(full_name_cpy); | 
| 306 | 0 |             return NC_ENOGRP; | 
| 307 | 0 |         } | 
| 308 | 0 |     } | 
| 309 | 0 |     else | 
| 310 | 0 |     { | 
| 311 |  |         /* Keep parsing the string. */ | 
| 312 | 0 |         for (; cp; id1 = id2) | 
| 313 | 0 |         { | 
| 314 | 0 |             if ((ret = NC4_inq_ncid(id1, cp, &id2))) | 
| 315 | 0 |             { | 
| 316 | 0 |                 free(full_name_cpy); | 
| 317 | 0 |                 return ret; | 
| 318 | 0 |             } | 
| 319 | 0 |             cp = strtok(NULL, "/"); | 
| 320 | 0 |         } | 
| 321 | 0 |     } | 
| 322 |  |  | 
| 323 |  |     /* Give the user the requested value. */ | 
| 324 | 0 |     if (grp_ncid) | 
| 325 | 0 |         *grp_ncid = id2; | 
| 326 |  | 
 | 
| 327 | 0 |     free(full_name_cpy); | 
| 328 |  | 
 | 
| 329 | 0 |     return NC_NOERR; | 
| 330 | 0 | } | 
| 331 |  |  | 
| 332 |  | /** | 
| 333 |  |  * @internal Get a list of ids for all the variables in a group. | 
| 334 |  |  * | 
| 335 |  |  * @param ncid File and group ID. | 
| 336 |  |  * @param nvars Pointer that gets number of vars in group. | 
| 337 |  |  * @param varids Pointer that gets array of var IDs. | 
| 338 |  |  * | 
| 339 |  |  * @return ::NC_NOERR No error. | 
| 340 |  |  * @return ::NC_EBADID Bad ncid. | 
| 341 |  |  * @author Ed Hartnett | 
| 342 |  |  */ | 
| 343 |  | int | 
| 344 |  | NC4_inq_varids(int ncid, int *nvars, int *varids) | 
| 345 | 0 | { | 
| 346 | 0 |     NC_GRP_INFO_T *grp; | 
| 347 | 0 |     NC_FILE_INFO_T *h5; | 
| 348 | 0 |     NC_VAR_INFO_T *var; | 
| 349 | 0 |     int num_vars = 0; | 
| 350 | 0 |     int retval; | 
| 351 |  | 
 | 
| 352 | 0 |     LOG((2, "nc_inq_varids: ncid 0x%x", ncid)); | 
| 353 |  |  | 
| 354 |  |     /* Find info for this file and group, and set pointer to each. */ | 
| 355 | 0 |     if ((retval = nc4_find_grp_h5(ncid, &grp, &h5))) | 
| 356 | 0 |         return retval; | 
| 357 | 0 |     assert(h5); | 
| 358 |  |  | 
| 359 |  |     /* This is a netCDF-4 group. Round up them doggies and count | 
| 360 |  |      * 'em. The list is in correct (i.e. creation) order. */ | 
| 361 | 0 |     for (size_t i=0; i < ncindexsize(grp->vars); i++) | 
| 362 | 0 |     { | 
| 363 | 0 |         var = (NC_VAR_INFO_T*)ncindexith(grp->vars,i); | 
| 364 | 0 |         if (!var) continue; | 
| 365 | 0 |         if (varids) | 
| 366 | 0 |             varids[num_vars] = var->hdr.id; | 
| 367 | 0 |         num_vars++; | 
| 368 | 0 |     } | 
| 369 |  |  | 
| 370 |  |     /* If the user wants to know how many vars in the group, tell | 
| 371 |  |      * him. */ | 
| 372 | 0 |     if (nvars) | 
| 373 | 0 |         *nvars = num_vars; | 
| 374 |  | 
 | 
| 375 | 0 |     return NC_NOERR; | 
| 376 | 0 | } | 
| 377 |  |  | 
| 378 |  | /** | 
| 379 |  |  * @internal This is the comparison function used for sorting dim | 
| 380 |  |  * ids. Integer comparison: returns negative if b > a and positive if | 
| 381 |  |  * a > b. | 
| 382 |  |  * | 
| 383 |  |  * @param a A pointer to an item to compare to b. | 
| 384 |  |  * @param b A pointer to an item to compare to a. | 
| 385 |  |  * | 
| 386 |  |  * @return a - b | 
| 387 |  |  * @author Ed Hartnett | 
| 388 |  |  */ | 
| 389 |  | int int_cmp(const void *a, const void *b) | 
| 390 | 0 | { | 
| 391 | 0 |     const int *ia = (const int *)a; | 
| 392 | 0 |     const int *ib = (const int *)b; | 
| 393 | 0 |     return *ia  - *ib; | 
| 394 | 0 | } | 
| 395 |  |  | 
| 396 |  | /** | 
| 397 |  |  * @internal Find all dimids for a location. This finds all dimensions | 
| 398 |  |  * in a group, with or without any of its parents, depending on last | 
| 399 |  |  * parameter. | 
| 400 |  |  * | 
| 401 |  |  * @param ncid File and group ID. | 
| 402 |  |  * @param ndims Pointer that gets number of dimensions available in group. | 
| 403 |  |  * @param dimids Pointer that gets dim IDs. | 
| 404 |  |  * @param include_parents If non-zero, include dimensions from parent | 
| 405 |  |  * groups. | 
| 406 |  |  * | 
| 407 |  |  * @return ::NC_NOERR No error. | 
| 408 |  |  * @return ::NC_EBADID Bad ncid. | 
| 409 |  |  * @author Ed Hartnett | 
| 410 |  |  */ | 
| 411 |  | int | 
| 412 |  | NC4_inq_dimids(int ncid, int *ndims, int *dimids, int include_parents) | 
| 413 | 0 | { | 
| 414 | 0 |     NC_GRP_INFO_T *grp, *g; | 
| 415 | 0 |     NC_FILE_INFO_T *h5; | 
| 416 | 0 |     NC_DIM_INFO_T *dim; | 
| 417 | 0 |     int num = 0; | 
| 418 | 0 |     int retval; | 
| 419 |  | 
 | 
| 420 | 0 |     LOG((2, "nc_inq_dimids: ncid 0x%x include_parents: %d", ncid, | 
| 421 | 0 |          include_parents)); | 
| 422 |  |  | 
| 423 |  |     /* Find info for this file and group, and set pointer to each. */ | 
| 424 | 0 |     if ((retval = nc4_find_grp_h5(ncid, &grp, &h5))) | 
| 425 | 0 |         return retval; | 
| 426 | 0 |     assert(h5); | 
| 427 |  |  | 
| 428 |  |     /* First count them. */ | 
| 429 | 0 |     num = ncindexcount(grp->dim); | 
| 430 | 0 |     if (include_parents) { | 
| 431 | 0 |         for (g = grp->parent; g; g = g->parent) | 
| 432 | 0 |             num += ncindexcount(g->dim); | 
| 433 | 0 |     } | 
| 434 |  |  | 
| 435 |  |     /* If the user wants the dimension ids, get them. */ | 
| 436 | 0 |     if (dimids) | 
| 437 | 0 |     { | 
| 438 | 0 |         int n = 0; | 
| 439 |  |  | 
| 440 |  |         /* Get dimension ids from this group. */ | 
| 441 | 0 |         for(size_t i=0;i<ncindexsize(grp->dim);i++) { | 
| 442 | 0 |             dim = (NC_DIM_INFO_T*)ncindexith(grp->dim,i); | 
| 443 | 0 |             if(dim == NULL) continue; | 
| 444 | 0 |             dimids[n++] = dim->hdr.id; | 
| 445 | 0 |         } | 
| 446 |  |  | 
| 447 |  |         /* Get dimension ids from parent groups. */ | 
| 448 | 0 |         if (include_parents) | 
| 449 | 0 |             for (g = grp->parent; g; g = g->parent) { | 
| 450 | 0 |                 for(size_t i=0;i<ncindexsize(g->dim);i++) { | 
| 451 | 0 |                     dim = (NC_DIM_INFO_T*)ncindexith(g->dim,i); | 
| 452 | 0 |                     if(dim == NULL) continue; | 
| 453 | 0 |                     dimids[n++] = dim->hdr.id; | 
| 454 | 0 |                 } | 
| 455 | 0 |             } | 
| 456 | 0 |         qsort(dimids, (size_t)num, sizeof(int), int_cmp); | 
| 457 | 0 |     } | 
| 458 |  |  | 
| 459 |  |     /* If the user wants the number of dims, give it. */ | 
| 460 | 0 |     if (ndims) | 
| 461 | 0 |         *ndims = num; | 
| 462 |  | 
 | 
| 463 | 0 |     return NC_NOERR; | 
| 464 | 0 | } |