]>
Commit | Line | Data |
---|---|---|
a12b51c4 | 1 | #include "util.h" |
cd0cfad7 | 2 | #include <api/fs/fs.h> |
a12b51c4 PM |
3 | #include "../perf.h" |
4 | #include "cpumap.h" | |
5 | #include <assert.h> | |
6 | #include <stdio.h> | |
86ee6e18 | 7 | #include <stdlib.h> |
f77b57ad | 8 | #include <linux/bitmap.h> |
f30a79b0 | 9 | #include "asm/bug.h" |
a12b51c4 | 10 | |
5ac76283 ACM |
11 | static int max_cpu_num; |
12 | static int max_node_num; | |
13 | static int *cpunode_map; | |
14 | ||
60d567e2 | 15 | static struct cpu_map *cpu_map__default_new(void) |
a12b51c4 | 16 | { |
60d567e2 ACM |
17 | struct cpu_map *cpus; |
18 | int nr_cpus; | |
a12b51c4 PM |
19 | |
20 | nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); | |
60d567e2 ACM |
21 | if (nr_cpus < 0) |
22 | return NULL; | |
23 | ||
24 | cpus = malloc(sizeof(*cpus) + nr_cpus * sizeof(int)); | |
25 | if (cpus != NULL) { | |
26 | int i; | |
27 | for (i = 0; i < nr_cpus; ++i) | |
28 | cpus->map[i] = i; | |
a12b51c4 | 29 | |
60d567e2 | 30 | cpus->nr = nr_cpus; |
f30a79b0 | 31 | atomic_set(&cpus->refcnt, 1); |
60d567e2 | 32 | } |
a12b51c4 | 33 | |
60d567e2 | 34 | return cpus; |
a12b51c4 PM |
35 | } |
36 | ||
60d567e2 | 37 | static struct cpu_map *cpu_map__trim_new(int nr_cpus, int *tmp_cpus) |
a12b51c4 | 38 | { |
60d567e2 ACM |
39 | size_t payload_size = nr_cpus * sizeof(int); |
40 | struct cpu_map *cpus = malloc(sizeof(*cpus) + payload_size); | |
41 | ||
42 | if (cpus != NULL) { | |
43 | cpus->nr = nr_cpus; | |
44 | memcpy(cpus->map, tmp_cpus, payload_size); | |
f30a79b0 | 45 | atomic_set(&cpus->refcnt, 1); |
60d567e2 ACM |
46 | } |
47 | ||
48 | return cpus; | |
49 | } | |
50 | ||
7ae92e74 | 51 | struct cpu_map *cpu_map__read(FILE *file) |
60d567e2 ACM |
52 | { |
53 | struct cpu_map *cpus = NULL; | |
a12b51c4 | 54 | int nr_cpus = 0; |
60d567e2 ACM |
55 | int *tmp_cpus = NULL, *tmp; |
56 | int max_entries = 0; | |
a12b51c4 PM |
57 | int n, cpu, prev; |
58 | char sep; | |
59 | ||
a12b51c4 PM |
60 | sep = 0; |
61 | prev = -1; | |
62 | for (;;) { | |
7ae92e74 | 63 | n = fscanf(file, "%u%c", &cpu, &sep); |
a12b51c4 PM |
64 | if (n <= 0) |
65 | break; | |
66 | if (prev >= 0) { | |
60d567e2 ACM |
67 | int new_max = nr_cpus + cpu - prev - 1; |
68 | ||
69 | if (new_max >= max_entries) { | |
70 | max_entries = new_max + MAX_NR_CPUS / 2; | |
71 | tmp = realloc(tmp_cpus, max_entries * sizeof(int)); | |
72 | if (tmp == NULL) | |
73 | goto out_free_tmp; | |
74 | tmp_cpus = tmp; | |
75 | } | |
76 | ||
a12b51c4 | 77 | while (++prev < cpu) |
60d567e2 ACM |
78 | tmp_cpus[nr_cpus++] = prev; |
79 | } | |
80 | if (nr_cpus == max_entries) { | |
81 | max_entries += MAX_NR_CPUS; | |
82 | tmp = realloc(tmp_cpus, max_entries * sizeof(int)); | |
83 | if (tmp == NULL) | |
84 | goto out_free_tmp; | |
85 | tmp_cpus = tmp; | |
a12b51c4 | 86 | } |
60d567e2 ACM |
87 | |
88 | tmp_cpus[nr_cpus++] = cpu; | |
a12b51c4 PM |
89 | if (n == 2 && sep == '-') |
90 | prev = cpu; | |
91 | else | |
92 | prev = -1; | |
93 | if (n == 1 || sep == '\n') | |
94 | break; | |
95 | } | |
a12b51c4 | 96 | |
60d567e2 ACM |
97 | if (nr_cpus > 0) |
98 | cpus = cpu_map__trim_new(nr_cpus, tmp_cpus); | |
99 | else | |
100 | cpus = cpu_map__default_new(); | |
101 | out_free_tmp: | |
102 | free(tmp_cpus); | |
7ae92e74 YZ |
103 | return cpus; |
104 | } | |
105 | ||
106 | static struct cpu_map *cpu_map__read_all_cpu_map(void) | |
107 | { | |
108 | struct cpu_map *cpus = NULL; | |
109 | FILE *onlnf; | |
110 | ||
111 | onlnf = fopen("/sys/devices/system/cpu/online", "r"); | |
112 | if (!onlnf) | |
113 | return cpu_map__default_new(); | |
114 | ||
115 | cpus = cpu_map__read(onlnf); | |
60d567e2 ACM |
116 | fclose(onlnf); |
117 | return cpus; | |
a12b51c4 | 118 | } |
c45c6ea2 | 119 | |
60d567e2 | 120 | struct cpu_map *cpu_map__new(const char *cpu_list) |
c45c6ea2 | 121 | { |
60d567e2 | 122 | struct cpu_map *cpus = NULL; |
c45c6ea2 SE |
123 | unsigned long start_cpu, end_cpu = 0; |
124 | char *p = NULL; | |
125 | int i, nr_cpus = 0; | |
60d567e2 ACM |
126 | int *tmp_cpus = NULL, *tmp; |
127 | int max_entries = 0; | |
c45c6ea2 SE |
128 | |
129 | if (!cpu_list) | |
60d567e2 | 130 | return cpu_map__read_all_cpu_map(); |
c45c6ea2 SE |
131 | |
132 | if (!isdigit(*cpu_list)) | |
60d567e2 | 133 | goto out; |
c45c6ea2 SE |
134 | |
135 | while (isdigit(*cpu_list)) { | |
136 | p = NULL; | |
137 | start_cpu = strtoul(cpu_list, &p, 0); | |
138 | if (start_cpu >= INT_MAX | |
139 | || (*p != '\0' && *p != ',' && *p != '-')) | |
140 | goto invalid; | |
141 | ||
142 | if (*p == '-') { | |
143 | cpu_list = ++p; | |
144 | p = NULL; | |
145 | end_cpu = strtoul(cpu_list, &p, 0); | |
146 | ||
147 | if (end_cpu >= INT_MAX || (*p != '\0' && *p != ',')) | |
148 | goto invalid; | |
149 | ||
150 | if (end_cpu < start_cpu) | |
151 | goto invalid; | |
152 | } else { | |
153 | end_cpu = start_cpu; | |
154 | } | |
155 | ||
156 | for (; start_cpu <= end_cpu; start_cpu++) { | |
157 | /* check for duplicates */ | |
158 | for (i = 0; i < nr_cpus; i++) | |
60d567e2 | 159 | if (tmp_cpus[i] == (int)start_cpu) |
c45c6ea2 SE |
160 | goto invalid; |
161 | ||
60d567e2 ACM |
162 | if (nr_cpus == max_entries) { |
163 | max_entries += MAX_NR_CPUS; | |
164 | tmp = realloc(tmp_cpus, max_entries * sizeof(int)); | |
165 | if (tmp == NULL) | |
166 | goto invalid; | |
167 | tmp_cpus = tmp; | |
168 | } | |
169 | tmp_cpus[nr_cpus++] = (int)start_cpu; | |
c45c6ea2 SE |
170 | } |
171 | if (*p) | |
172 | ++p; | |
173 | ||
174 | cpu_list = p; | |
175 | } | |
c45c6ea2 | 176 | |
60d567e2 ACM |
177 | if (nr_cpus > 0) |
178 | cpus = cpu_map__trim_new(nr_cpus, tmp_cpus); | |
179 | else | |
180 | cpus = cpu_map__default_new(); | |
c45c6ea2 | 181 | invalid: |
60d567e2 ACM |
182 | free(tmp_cpus); |
183 | out: | |
184 | return cpus; | |
185 | } | |
186 | ||
f77b57ad JO |
187 | static struct cpu_map *cpu_map__from_entries(struct cpu_map_entries *cpus) |
188 | { | |
189 | struct cpu_map *map; | |
190 | ||
191 | map = cpu_map__empty_new(cpus->nr); | |
192 | if (map) { | |
193 | unsigned i; | |
194 | ||
15d2b995 JO |
195 | for (i = 0; i < cpus->nr; i++) { |
196 | /* | |
197 | * Special treatment for -1, which is not real cpu number, | |
198 | * and we need to use (int) -1 to initialize map[i], | |
199 | * otherwise it would become 65535. | |
200 | */ | |
201 | if (cpus->cpu[i] == (u16) -1) | |
202 | map->map[i] = -1; | |
203 | else | |
204 | map->map[i] = (int) cpus->cpu[i]; | |
205 | } | |
f77b57ad JO |
206 | } |
207 | ||
208 | return map; | |
209 | } | |
210 | ||
211 | static struct cpu_map *cpu_map__from_mask(struct cpu_map_mask *mask) | |
212 | { | |
213 | struct cpu_map *map; | |
214 | int nr, nbits = mask->nr * mask->long_size * BITS_PER_BYTE; | |
215 | ||
216 | nr = bitmap_weight(mask->mask, nbits); | |
217 | ||
218 | map = cpu_map__empty_new(nr); | |
219 | if (map) { | |
220 | int cpu, i = 0; | |
221 | ||
222 | for_each_set_bit(cpu, mask->mask, nbits) | |
223 | map->map[i++] = cpu; | |
224 | } | |
225 | return map; | |
226 | ||
227 | } | |
228 | ||
229 | struct cpu_map *cpu_map__new_data(struct cpu_map_data *data) | |
230 | { | |
231 | if (data->type == PERF_CPU_MAP__CPUS) | |
232 | return cpu_map__from_entries((struct cpu_map_entries *)data->data); | |
233 | else | |
234 | return cpu_map__from_mask((struct cpu_map_mask *)data->data); | |
235 | } | |
236 | ||
9ae7d335 ACM |
237 | size_t cpu_map__fprintf(struct cpu_map *map, FILE *fp) |
238 | { | |
239 | int i; | |
240 | size_t printed = fprintf(fp, "%d cpu%s: ", | |
241 | map->nr, map->nr > 1 ? "s" : ""); | |
242 | for (i = 0; i < map->nr; ++i) | |
243 | printed += fprintf(fp, "%s%d", i ? ", " : "", map->map[i]); | |
244 | ||
245 | return printed + fprintf(fp, "\n"); | |
246 | } | |
247 | ||
60d567e2 ACM |
248 | struct cpu_map *cpu_map__dummy_new(void) |
249 | { | |
250 | struct cpu_map *cpus = malloc(sizeof(*cpus) + sizeof(int)); | |
251 | ||
252 | if (cpus != NULL) { | |
253 | cpus->nr = 1; | |
254 | cpus->map[0] = -1; | |
f30a79b0 | 255 | atomic_set(&cpus->refcnt, 1); |
60d567e2 ACM |
256 | } |
257 | ||
258 | return cpus; | |
c45c6ea2 | 259 | } |
915fce20 | 260 | |
2322f573 JO |
261 | struct cpu_map *cpu_map__empty_new(int nr) |
262 | { | |
263 | struct cpu_map *cpus = malloc(sizeof(*cpus) + sizeof(int) * nr); | |
264 | ||
265 | if (cpus != NULL) { | |
266 | int i; | |
267 | ||
268 | cpus->nr = nr; | |
269 | for (i = 0; i < nr; i++) | |
270 | cpus->map[i] = -1; | |
271 | ||
272 | atomic_set(&cpus->refcnt, 1); | |
273 | } | |
274 | ||
275 | return cpus; | |
276 | } | |
277 | ||
f30a79b0 | 278 | static void cpu_map__delete(struct cpu_map *map) |
915fce20 | 279 | { |
f30a79b0 JO |
280 | if (map) { |
281 | WARN_ONCE(atomic_read(&map->refcnt) != 0, | |
282 | "cpu_map refcnt unbalanced\n"); | |
283 | free(map); | |
284 | } | |
285 | } | |
286 | ||
287 | struct cpu_map *cpu_map__get(struct cpu_map *map) | |
288 | { | |
289 | if (map) | |
290 | atomic_inc(&map->refcnt); | |
291 | return map; | |
292 | } | |
293 | ||
294 | void cpu_map__put(struct cpu_map *map) | |
295 | { | |
296 | if (map && atomic_dec_and_test(&map->refcnt)) | |
297 | cpu_map__delete(map); | |
915fce20 | 298 | } |
5ac59a8a | 299 | |
5d8cf721 | 300 | static int cpu__get_topology_int(int cpu, const char *name, int *value) |
5ac59a8a | 301 | { |
5ac59a8a | 302 | char path[PATH_MAX]; |
5ac59a8a | 303 | |
86ee6e18 | 304 | snprintf(path, PATH_MAX, |
5d8cf721 | 305 | "devices/system/cpu/cpu%d/topology/%s", cpu, name); |
5ac59a8a | 306 | |
5d8cf721 ACM |
307 | return sysfs__read_int(path, value); |
308 | } | |
193b6bd3 | 309 | |
5d8cf721 ACM |
310 | int cpu_map__get_socket_id(int cpu) |
311 | { | |
312 | int value, ret = cpu__get_topology_int(cpu, "physical_package_id", &value); | |
313 | return ret ?: value; | |
193b6bd3 KL |
314 | } |
315 | ||
1fe7a300 | 316 | int cpu_map__get_socket(struct cpu_map *map, int idx, void *data __maybe_unused) |
193b6bd3 KL |
317 | { |
318 | int cpu; | |
319 | ||
320 | if (idx > map->nr) | |
321 | return -1; | |
322 | ||
323 | cpu = map->map[idx]; | |
324 | ||
325 | return cpu_map__get_socket_id(cpu); | |
5ac59a8a SE |
326 | } |
327 | ||
86ee6e18 | 328 | static int cmp_ids(const void *a, const void *b) |
5ac59a8a | 329 | { |
86ee6e18 SE |
330 | return *(int *)a - *(int *)b; |
331 | } | |
332 | ||
f1cbb8f3 | 333 | int cpu_map__build_map(struct cpu_map *cpus, struct cpu_map **res, |
1fe7a300 JO |
334 | int (*f)(struct cpu_map *map, int cpu, void *data), |
335 | void *data) | |
86ee6e18 SE |
336 | { |
337 | struct cpu_map *c; | |
5ac59a8a SE |
338 | int nr = cpus->nr; |
339 | int cpu, s1, s2; | |
340 | ||
86ee6e18 SE |
341 | /* allocate as much as possible */ |
342 | c = calloc(1, sizeof(*c) + nr * sizeof(int)); | |
343 | if (!c) | |
5ac59a8a SE |
344 | return -1; |
345 | ||
346 | for (cpu = 0; cpu < nr; cpu++) { | |
1fe7a300 | 347 | s1 = f(cpus, cpu, data); |
86ee6e18 SE |
348 | for (s2 = 0; s2 < c->nr; s2++) { |
349 | if (s1 == c->map[s2]) | |
5ac59a8a SE |
350 | break; |
351 | } | |
86ee6e18 SE |
352 | if (s2 == c->nr) { |
353 | c->map[c->nr] = s1; | |
354 | c->nr++; | |
5ac59a8a SE |
355 | } |
356 | } | |
86ee6e18 SE |
357 | /* ensure we process id in increasing order */ |
358 | qsort(c->map, c->nr, sizeof(int), cmp_ids); | |
359 | ||
bc1d0368 | 360 | atomic_set(&c->refcnt, 1); |
86ee6e18 | 361 | *res = c; |
5ac59a8a SE |
362 | return 0; |
363 | } | |
86ee6e18 | 364 | |
193b6bd3 | 365 | int cpu_map__get_core_id(int cpu) |
12c08a9f | 366 | { |
5d8cf721 ACM |
367 | int value, ret = cpu__get_topology_int(cpu, "core_id", &value); |
368 | return ret ?: value; | |
193b6bd3 KL |
369 | } |
370 | ||
1fe7a300 | 371 | int cpu_map__get_core(struct cpu_map *map, int idx, void *data) |
193b6bd3 KL |
372 | { |
373 | int cpu, s; | |
374 | ||
375 | if (idx > map->nr) | |
12c08a9f SE |
376 | return -1; |
377 | ||
193b6bd3 KL |
378 | cpu = map->map[idx]; |
379 | ||
380 | cpu = cpu_map__get_core_id(cpu); | |
381 | ||
1fe7a300 | 382 | s = cpu_map__get_socket(map, idx, data); |
12c08a9f SE |
383 | if (s == -1) |
384 | return -1; | |
385 | ||
386 | /* | |
387 | * encode socket in upper 16 bits | |
388 | * core_id is relative to socket, and | |
389 | * we need a global id. So we combine | |
390 | * socket+ core id | |
391 | */ | |
392 | return (s << 16) | (cpu & 0xffff); | |
393 | } | |
394 | ||
86ee6e18 SE |
395 | int cpu_map__build_socket_map(struct cpu_map *cpus, struct cpu_map **sockp) |
396 | { | |
1fe7a300 | 397 | return cpu_map__build_map(cpus, sockp, cpu_map__get_socket, NULL); |
86ee6e18 | 398 | } |
12c08a9f SE |
399 | |
400 | int cpu_map__build_core_map(struct cpu_map *cpus, struct cpu_map **corep) | |
401 | { | |
1fe7a300 | 402 | return cpu_map__build_map(cpus, corep, cpu_map__get_core, NULL); |
12c08a9f | 403 | } |
7780c25b DZ |
404 | |
405 | /* setup simple routines to easily access node numbers given a cpu number */ | |
406 | static int get_max_num(char *path, int *max) | |
407 | { | |
408 | size_t num; | |
409 | char *buf; | |
410 | int err = 0; | |
411 | ||
412 | if (filename__read_str(path, &buf, &num)) | |
413 | return -1; | |
414 | ||
415 | buf[num] = '\0'; | |
416 | ||
417 | /* start on the right, to find highest node num */ | |
418 | while (--num) { | |
419 | if ((buf[num] == ',') || (buf[num] == '-')) { | |
420 | num++; | |
421 | break; | |
422 | } | |
423 | } | |
424 | if (sscanf(&buf[num], "%d", max) < 1) { | |
425 | err = -1; | |
426 | goto out; | |
427 | } | |
428 | ||
429 | /* convert from 0-based to 1-based */ | |
430 | (*max)++; | |
431 | ||
432 | out: | |
433 | free(buf); | |
434 | return err; | |
435 | } | |
436 | ||
437 | /* Determine highest possible cpu in the system for sparse allocation */ | |
438 | static void set_max_cpu_num(void) | |
439 | { | |
440 | const char *mnt; | |
441 | char path[PATH_MAX]; | |
442 | int ret = -1; | |
443 | ||
444 | /* set up default */ | |
445 | max_cpu_num = 4096; | |
446 | ||
447 | mnt = sysfs__mountpoint(); | |
448 | if (!mnt) | |
449 | goto out; | |
450 | ||
451 | /* get the highest possible cpu number for a sparse allocation */ | |
f5b1f4e4 | 452 | ret = snprintf(path, PATH_MAX, "%s/devices/system/cpu/possible", mnt); |
7780c25b DZ |
453 | if (ret == PATH_MAX) { |
454 | pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX); | |
455 | goto out; | |
456 | } | |
457 | ||
458 | ret = get_max_num(path, &max_cpu_num); | |
459 | ||
460 | out: | |
461 | if (ret) | |
462 | pr_err("Failed to read max cpus, using default of %d\n", max_cpu_num); | |
463 | } | |
464 | ||
465 | /* Determine highest possible node in the system for sparse allocation */ | |
466 | static void set_max_node_num(void) | |
467 | { | |
468 | const char *mnt; | |
469 | char path[PATH_MAX]; | |
470 | int ret = -1; | |
471 | ||
472 | /* set up default */ | |
473 | max_node_num = 8; | |
474 | ||
475 | mnt = sysfs__mountpoint(); | |
476 | if (!mnt) | |
477 | goto out; | |
478 | ||
479 | /* get the highest possible cpu number for a sparse allocation */ | |
480 | ret = snprintf(path, PATH_MAX, "%s/devices/system/node/possible", mnt); | |
481 | if (ret == PATH_MAX) { | |
482 | pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX); | |
483 | goto out; | |
484 | } | |
485 | ||
486 | ret = get_max_num(path, &max_node_num); | |
487 | ||
488 | out: | |
489 | if (ret) | |
490 | pr_err("Failed to read max nodes, using default of %d\n", max_node_num); | |
491 | } | |
492 | ||
5ac76283 ACM |
493 | int cpu__max_node(void) |
494 | { | |
495 | if (unlikely(!max_node_num)) | |
496 | set_max_node_num(); | |
497 | ||
498 | return max_node_num; | |
499 | } | |
500 | ||
501 | int cpu__max_cpu(void) | |
502 | { | |
503 | if (unlikely(!max_cpu_num)) | |
504 | set_max_cpu_num(); | |
505 | ||
506 | return max_cpu_num; | |
507 | } | |
508 | ||
509 | int cpu__get_node(int cpu) | |
510 | { | |
511 | if (unlikely(cpunode_map == NULL)) { | |
512 | pr_debug("cpu_map not initialized\n"); | |
513 | return -1; | |
514 | } | |
515 | ||
516 | return cpunode_map[cpu]; | |
517 | } | |
518 | ||
7780c25b DZ |
519 | static int init_cpunode_map(void) |
520 | { | |
521 | int i; | |
522 | ||
523 | set_max_cpu_num(); | |
524 | set_max_node_num(); | |
525 | ||
526 | cpunode_map = calloc(max_cpu_num, sizeof(int)); | |
527 | if (!cpunode_map) { | |
528 | pr_err("%s: calloc failed\n", __func__); | |
529 | return -1; | |
530 | } | |
531 | ||
532 | for (i = 0; i < max_cpu_num; i++) | |
533 | cpunode_map[i] = -1; | |
534 | ||
535 | return 0; | |
536 | } | |
537 | ||
538 | int cpu__setup_cpunode_map(void) | |
539 | { | |
540 | struct dirent *dent1, *dent2; | |
541 | DIR *dir1, *dir2; | |
542 | unsigned int cpu, mem; | |
543 | char buf[PATH_MAX]; | |
544 | char path[PATH_MAX]; | |
545 | const char *mnt; | |
546 | int n; | |
547 | ||
548 | /* initialize globals */ | |
549 | if (init_cpunode_map()) | |
550 | return -1; | |
551 | ||
552 | mnt = sysfs__mountpoint(); | |
553 | if (!mnt) | |
554 | return 0; | |
555 | ||
556 | n = snprintf(path, PATH_MAX, "%s/devices/system/node", mnt); | |
557 | if (n == PATH_MAX) { | |
558 | pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX); | |
559 | return -1; | |
560 | } | |
561 | ||
562 | dir1 = opendir(path); | |
563 | if (!dir1) | |
564 | return 0; | |
565 | ||
566 | /* walk tree and setup map */ | |
567 | while ((dent1 = readdir(dir1)) != NULL) { | |
568 | if (dent1->d_type != DT_DIR || sscanf(dent1->d_name, "node%u", &mem) < 1) | |
569 | continue; | |
570 | ||
571 | n = snprintf(buf, PATH_MAX, "%s/%s", path, dent1->d_name); | |
572 | if (n == PATH_MAX) { | |
573 | pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX); | |
574 | continue; | |
575 | } | |
576 | ||
577 | dir2 = opendir(buf); | |
578 | if (!dir2) | |
579 | continue; | |
580 | while ((dent2 = readdir(dir2)) != NULL) { | |
581 | if (dent2->d_type != DT_LNK || sscanf(dent2->d_name, "cpu%u", &cpu) < 1) | |
582 | continue; | |
583 | cpunode_map[cpu] = mem; | |
584 | } | |
585 | closedir(dir2); | |
586 | } | |
587 | closedir(dir1); | |
588 | return 0; | |
589 | } |