]>
Commit | Line | Data |
---|---|---|
221cc3ce MT |
1 | /*############################################################################# |
2 | # # | |
3 | # Pakfire - The IPFire package management system # | |
4 | # Copyright (C) 2014 Pakfire development team # | |
5 | # # | |
6 | # This program is free software: you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation, either version 3 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # This program is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
18 | # # | |
19 | #############################################################################*/ | |
20 | ||
5e9463ec | 21 | #include <errno.h> |
3fca5032 | 22 | #include <libgen.h> |
c98132d1 | 23 | #include <linux/limits.h> |
221cc3ce MT |
24 | #include <stdlib.h> |
25 | #include <string.h> | |
221cc3ce | 26 | #include <sys/types.h> |
221cc3ce MT |
27 | #include <time.h> |
28 | ||
9a6e3e2d | 29 | #include <archive_entry.h> |
5e8dfbeb | 30 | #include <openssl/evp.h> |
9a6e3e2d | 31 | |
221cc3ce MT |
32 | #include <pakfire/constants.h> |
33 | #include <pakfire/file.h> | |
3fca5032 | 34 | #include <pakfire/logging.h> |
883b3be9 | 35 | #include <pakfire/pakfire.h> |
9f953e68 | 36 | #include <pakfire/private.h> |
d973a13d | 37 | #include <pakfire/string.h> |
221cc3ce MT |
38 | #include <pakfire/util.h> |
39 | ||
65131b30 MT |
40 | #define MAX_DIGESTS 4 |
41 | ||
42 | struct pakfire_file_digest { | |
43 | enum pakfire_digests type; | |
44 | unsigned char digest[EVP_MAX_MD_SIZE]; | |
45 | size_t length; | |
46 | ||
47 | // Add a buffer to store the hex representation | |
48 | char* hexdigest; | |
49 | }; | |
50 | ||
5803b5f6 | 51 | struct pakfire_file { |
ac4c607b | 52 | struct pakfire* pakfire; |
5e9463ec | 53 | int nrefs; |
221cc3ce | 54 | |
3b9e3970 | 55 | char path[PATH_MAX]; |
0027da6f | 56 | char abspath[PATH_MAX]; |
5e9463ec | 57 | ssize_t size; |
221cc3ce | 58 | |
c98132d1 MT |
59 | char user[256]; |
60 | char group[256]; | |
221cc3ce | 61 | |
5e9463ec | 62 | mode_t mode; |
487d6485 | 63 | dev_t dev; |
ef4e8460 MT |
64 | |
65 | time_t ctime; | |
66 | time_t mtime; | |
d03fa9a3 | 67 | |
59d8c727 MT |
68 | // Link destinations |
69 | char hardlink[PATH_MAX]; | |
70 | char symlink[PATH_MAX]; | |
71 | ||
65131b30 MT |
72 | // Digests |
73 | struct pakfire_file_digest digests[MAX_DIGESTS]; | |
5e9463ec MT |
74 | |
75 | #warning TODO capabilities, config, data | |
76 | // capabilities | |
77 | //int is_configfile; | |
78 | //int is_datafile; | |
79 | }; | |
80 | ||
ac4c607b | 81 | PAKFIRE_EXPORT int pakfire_file_create(struct pakfire_file** file, struct pakfire* pakfire) { |
5803b5f6 | 82 | struct pakfire_file* f = calloc(1, sizeof(*f)); |
5e9463ec | 83 | if (!f) |
7403779a | 84 | return 1; |
5e9463ec | 85 | |
883b3be9 | 86 | f->pakfire = pakfire_ref(pakfire); |
5e9463ec MT |
87 | f->nrefs = 1; |
88 | ||
89 | *file = f; | |
90 | return 0; | |
d03fa9a3 MT |
91 | } |
92 | ||
ac4c607b | 93 | int pakfire_file_create_from_archive_entry(struct pakfire_file** file, struct pakfire* pakfire, |
d0985e31 MT |
94 | struct archive_entry* entry) { |
95 | int r = pakfire_file_create(file, pakfire); | |
96 | if (r) | |
97 | return r; | |
98 | ||
99 | // Copy archive entry | |
100 | r = pakfire_file_copy_archive_entry(*file, entry); | |
101 | if (r) | |
102 | goto ERROR; | |
103 | ||
104 | return 0; | |
105 | ||
106 | ERROR: | |
107 | pakfire_file_unref(*file); | |
108 | *file = NULL; | |
109 | ||
110 | return r; | |
111 | } | |
112 | ||
aa8ea50c MT |
113 | static const struct pakfire_libarchive_digest { |
114 | enum pakfire_digests pakfire; | |
115 | int libarchive; | |
116 | } pakfire_libarchive_digests[] = { | |
117 | { PAKFIRE_DIGEST_SHA512, ARCHIVE_ENTRY_DIGEST_SHA512 }, | |
118 | { PAKFIRE_DIGEST_SHA256, ARCHIVE_ENTRY_DIGEST_SHA256 }, | |
119 | { PAKFIRE_DIGEST_SHA1, ARCHIVE_ENTRY_DIGEST_SHA1 }, | |
120 | { PAKFIRE_DIGEST_NONE, 0 }, | |
121 | }; | |
122 | ||
5803b5f6 | 123 | int pakfire_file_copy_archive_entry(struct pakfire_file* file, struct archive_entry* entry) { |
aa8ea50c MT |
124 | int r = 0; |
125 | ||
3b9e3970 MT |
126 | // Set abspath |
127 | pakfire_file_set_abspath(file, archive_entry_sourcepath(entry)); | |
128 | ||
32485f6c | 129 | // Set path |
b9f703e5 MT |
130 | const char* path = archive_entry_pathname(entry); |
131 | if (path) { | |
132 | // Strip any leading dots from paths | |
133 | if (pakfire_string_startswith(path, "./")) | |
134 | path++; | |
135 | ||
136 | pakfire_file_set_path(file, path); | |
137 | } | |
9a6e3e2d | 138 | |
59d8c727 MT |
139 | // Set links |
140 | pakfire_file_set_hardlink(file, archive_entry_hardlink(entry)); | |
141 | pakfire_file_set_symlink(file, archive_entry_symlink(entry)); | |
142 | ||
9a6e3e2d MT |
143 | // Set size |
144 | pakfire_file_set_size(file, archive_entry_size(entry)); | |
145 | ||
146 | // Set mode | |
147 | pakfire_file_set_mode(file, archive_entry_mode(entry)); | |
148 | ||
487d6485 MT |
149 | // Set dev type |
150 | if (archive_entry_dev_is_set(entry)) | |
151 | pakfire_file_set_dev(file, archive_entry_dev(entry)); | |
152 | ||
9a6e3e2d MT |
153 | // Set user |
154 | pakfire_file_set_user(file, archive_entry_uname(entry)); | |
155 | ||
156 | // Set group | |
157 | pakfire_file_set_group(file, archive_entry_gname(entry)); | |
158 | ||
ef4e8460 MT |
159 | // Set times |
160 | pakfire_file_set_ctime(file, archive_entry_ctime(entry)); | |
161 | pakfire_file_set_mtime(file, archive_entry_mtime(entry)); | |
9a6e3e2d | 162 | |
aa8ea50c MT |
163 | // Copy digest |
164 | for (const struct pakfire_libarchive_digest* type = pakfire_libarchive_digests; | |
165 | type->pakfire; type++) { | |
166 | const unsigned char* digest = archive_entry_digest(entry, type->libarchive); | |
167 | if (digest) { | |
65131b30 MT |
168 | size_t length = pakfire_digest_length(type->pakfire); |
169 | ||
170 | r = pakfire_file_set_digest(file, type->pakfire, digest, length); | |
aa8ea50c MT |
171 | if (r) |
172 | return r; | |
aa8ea50c MT |
173 | } |
174 | } | |
175 | ||
176 | return r; | |
9a6e3e2d MT |
177 | } |
178 | ||
5acd852e MT |
179 | struct archive_entry* pakfire_file_archive_entry(struct pakfire_file* file) { |
180 | struct archive_entry* entry = archive_entry_new(); | |
181 | if (!entry) { | |
182 | ERROR(file->pakfire, "Could not allocate archive entry: %m\n"); | |
183 | return NULL; | |
184 | } | |
185 | ||
186 | // Set path | |
187 | archive_entry_copy_pathname(entry, pakfire_file_get_path(file)); | |
188 | ||
189 | // Set source path | |
190 | archive_entry_copy_sourcepath(entry, file->abspath); | |
191 | ||
192 | // Set links | |
193 | if (*file->hardlink) | |
194 | archive_entry_set_hardlink(entry, file->hardlink); | |
195 | if (*file->symlink) | |
196 | archive_entry_set_symlink(entry, file->symlink); | |
197 | ||
198 | // Set size | |
199 | archive_entry_set_size(entry, pakfire_file_get_size(file)); | |
200 | ||
201 | // Set mode | |
202 | archive_entry_set_mode(entry, pakfire_file_get_mode(file)); | |
203 | ||
204 | // Set user | |
205 | archive_entry_set_uname(entry, pakfire_file_get_user(file)); | |
206 | ||
207 | // Set group | |
208 | archive_entry_set_gname(entry, pakfire_file_get_group(file)); | |
209 | ||
210 | // Set times | |
211 | archive_entry_set_ctime(entry, pakfire_file_get_ctime(file), 0); | |
212 | archive_entry_set_mtime(entry, pakfire_file_get_mtime(file), 0); | |
213 | ||
214 | // XXX copy digest | |
215 | ||
216 | return entry; | |
217 | } | |
218 | ||
5803b5f6 | 219 | static void pakfire_file_free(struct pakfire_file* file) { |
65131b30 MT |
220 | struct pakfire_file_digest* digest = NULL; |
221 | ||
222 | // Free any generated hexdigests | |
223 | for (unsigned int i = 0; i < MAX_DIGESTS; i++) { | |
224 | digest = &file->digests[i]; | |
225 | ||
baf8b0ed | 226 | if (digest->hexdigest) { |
65131b30 | 227 | free(digest->hexdigest); |
baf8b0ed MT |
228 | digest->hexdigest = NULL; |
229 | } | |
65131b30 | 230 | } |
221cc3ce | 231 | |
5e8dfbeb | 232 | pakfire_unref(file->pakfire); |
f0d6233d | 233 | free(file); |
221cc3ce MT |
234 | } |
235 | ||
5803b5f6 | 236 | PAKFIRE_EXPORT struct pakfire_file* pakfire_file_ref(struct pakfire_file* file) { |
5e9463ec | 237 | file->nrefs++; |
221cc3ce | 238 | |
5e9463ec MT |
239 | return file; |
240 | } | |
221cc3ce | 241 | |
5803b5f6 | 242 | PAKFIRE_EXPORT struct pakfire_file* pakfire_file_unref(struct pakfire_file* file) { |
5e9463ec MT |
243 | if (--file->nrefs > 0) |
244 | return file; | |
245 | ||
246 | pakfire_file_free(file); | |
247 | return NULL; | |
221cc3ce MT |
248 | } |
249 | ||
5803b5f6 | 250 | PAKFIRE_EXPORT int pakfire_file_cmp(struct pakfire_file* file1, struct pakfire_file* file2) { |
32485f6c MT |
251 | const char* path1 = pakfire_file_get_path(file1); |
252 | const char* path2 = pakfire_file_get_path(file2); | |
221cc3ce | 253 | |
32485f6c | 254 | return strcmp(path1, path2); |
221cc3ce MT |
255 | } |
256 | ||
5803b5f6 | 257 | const char* pakfire_file_get_abspath(struct pakfire_file* file) { |
e4c2f7a9 MT |
258 | return file->abspath; |
259 | } | |
260 | ||
5803b5f6 | 261 | int pakfire_file_set_abspath(struct pakfire_file* file, const char* path) { |
e7917fb3 | 262 | return pakfire_string_set(file->abspath, path); |
3b9e3970 MT |
263 | } |
264 | ||
5803b5f6 | 265 | PAKFIRE_EXPORT const char* pakfire_file_get_path(struct pakfire_file* file) { |
32485f6c | 266 | return file->path; |
221cc3ce MT |
267 | } |
268 | ||
5803b5f6 | 269 | PAKFIRE_EXPORT int pakfire_file_set_path(struct pakfire_file* file, const char* path) { |
e7917fb3 | 270 | return pakfire_string_set(file->path, path); |
221cc3ce MT |
271 | } |
272 | ||
59d8c727 MT |
273 | PAKFIRE_EXPORT const char* pakfire_file_get_hardlink(struct pakfire_file* file) { |
274 | if (!*file->hardlink) | |
275 | return NULL; | |
276 | ||
277 | return file->hardlink; | |
278 | } | |
279 | ||
280 | PAKFIRE_EXPORT void pakfire_file_set_hardlink(struct pakfire_file* file, const char* link) { | |
281 | if (!link || !*link) | |
282 | *file->hardlink = '\0'; | |
283 | else | |
284 | pakfire_string_set(file->hardlink, link); | |
285 | } | |
286 | ||
287 | PAKFIRE_EXPORT const char* pakfire_file_get_symlink(struct pakfire_file* file) { | |
288 | if (!*file->symlink) | |
289 | return NULL; | |
290 | ||
291 | return file->symlink; | |
292 | } | |
293 | ||
294 | PAKFIRE_EXPORT void pakfire_file_set_symlink(struct pakfire_file* file, const char* link) { | |
295 | if (!link || !*link) | |
296 | *file->hardlink = '\0'; | |
297 | else | |
298 | pakfire_string_set(file->symlink, link); | |
299 | } | |
300 | ||
5803b5f6 | 301 | PAKFIRE_EXPORT int pakfire_file_get_type(struct pakfire_file* file) { |
f9770f19 | 302 | return file->mode & S_IFMT; |
221cc3ce MT |
303 | } |
304 | ||
5803b5f6 | 305 | PAKFIRE_EXPORT ssize_t pakfire_file_get_size(struct pakfire_file* file) { |
221cc3ce MT |
306 | return file->size; |
307 | } | |
308 | ||
5803b5f6 | 309 | PAKFIRE_EXPORT void pakfire_file_set_size(struct pakfire_file* file, ssize_t size) { |
221cc3ce MT |
310 | file->size = size; |
311 | } | |
312 | ||
5803b5f6 | 313 | PAKFIRE_EXPORT const char* pakfire_file_get_user(struct pakfire_file* file) { |
221cc3ce MT |
314 | return file->user; |
315 | } | |
316 | ||
5803b5f6 | 317 | PAKFIRE_EXPORT void pakfire_file_set_user(struct pakfire_file* file, const char* user) { |
e7917fb3 | 318 | pakfire_string_set(file->user, user); |
221cc3ce MT |
319 | } |
320 | ||
5803b5f6 | 321 | PAKFIRE_EXPORT const char* pakfire_file_get_group(struct pakfire_file* file) { |
221cc3ce MT |
322 | return file->group; |
323 | } | |
324 | ||
5803b5f6 | 325 | PAKFIRE_EXPORT void pakfire_file_set_group(struct pakfire_file* file, const char* group) { |
e7917fb3 | 326 | pakfire_string_set(file->group, group); |
221cc3ce MT |
327 | } |
328 | ||
5803b5f6 | 329 | PAKFIRE_EXPORT mode_t pakfire_file_get_mode(struct pakfire_file* file) { |
221cc3ce MT |
330 | return file->mode; |
331 | } | |
332 | ||
5803b5f6 | 333 | PAKFIRE_EXPORT void pakfire_file_set_mode(struct pakfire_file* file, mode_t mode) { |
221cc3ce MT |
334 | file->mode = mode; |
335 | } | |
336 | ||
487d6485 MT |
337 | PAKFIRE_EXPORT dev_t pakfire_file_get_dev(struct pakfire_file* file) { |
338 | return file->dev; | |
339 | } | |
340 | ||
341 | PAKFIRE_EXPORT void pakfire_file_set_dev(struct pakfire_file* file, dev_t dev) { | |
342 | file->dev = dev; | |
343 | } | |
344 | ||
5803b5f6 | 345 | PAKFIRE_EXPORT time_t pakfire_file_get_ctime(struct pakfire_file* file) { |
ef4e8460 MT |
346 | return file->ctime; |
347 | } | |
348 | ||
5803b5f6 | 349 | PAKFIRE_EXPORT void pakfire_file_set_ctime(struct pakfire_file* file, time_t time) { |
ef4e8460 MT |
350 | file->ctime = time; |
351 | } | |
352 | ||
5803b5f6 | 353 | PAKFIRE_EXPORT time_t pakfire_file_get_mtime(struct pakfire_file* file) { |
ef4e8460 | 354 | return file->mtime; |
221cc3ce MT |
355 | } |
356 | ||
5803b5f6 | 357 | PAKFIRE_EXPORT void pakfire_file_set_mtime(struct pakfire_file* file, time_t time) { |
ef4e8460 | 358 | file->mtime = time; |
221cc3ce MT |
359 | } |
360 | ||
65131b30 MT |
361 | static struct pakfire_file_digest* pakfire_file_find_digest( |
362 | struct pakfire_file* file, enum pakfire_digests type) { | |
363 | struct pakfire_file_digest* digest = NULL; | |
364 | ||
365 | for (unsigned int i = 0; i < MAX_DIGESTS; i++) { | |
366 | digest = &file->digests[i]; | |
5e8dfbeb | 367 | |
65131b30 MT |
368 | if (digest->type == type) |
369 | return digest; | |
370 | } | |
5e8dfbeb | 371 | |
65131b30 | 372 | // No match |
5e8dfbeb MT |
373 | return NULL; |
374 | } | |
375 | ||
65131b30 MT |
376 | PAKFIRE_EXPORT const unsigned char* pakfire_file_get_digest( |
377 | struct pakfire_file* file, enum pakfire_digests type, size_t* length) { | |
378 | const struct pakfire_file_digest* digest = pakfire_file_find_digest(file, type); | |
379 | if (!digest) | |
380 | return NULL; | |
381 | ||
382 | // Export length | |
383 | if (length) | |
384 | *length = digest->length; | |
385 | ||
386 | return digest->digest; | |
387 | } | |
388 | ||
5e8dfbeb | 389 | PAKFIRE_EXPORT const char* pakfire_file_get_hexdigest( |
65131b30 MT |
390 | struct pakfire_file* file, enum pakfire_digests type) { |
391 | struct pakfire_file_digest* digest = pakfire_file_find_digest(file, type); | |
392 | if (!digest) | |
393 | return NULL; | |
5e8dfbeb | 394 | |
65131b30 MT |
395 | // Generate the hexdigest if non exists |
396 | if (!digest->hexdigest) { | |
397 | const size_t length = pakfire_digest_length(digest->type); | |
398 | if (!length) | |
5e8dfbeb MT |
399 | return NULL; |
400 | ||
65131b30 | 401 | digest->hexdigest = __pakfire_hexlify(digest->digest, length); |
5e8dfbeb MT |
402 | } |
403 | ||
65131b30 | 404 | return digest->hexdigest; |
5e8dfbeb MT |
405 | } |
406 | ||
407 | PAKFIRE_EXPORT int pakfire_file_set_digest(struct pakfire_file* file, | |
65131b30 MT |
408 | enum pakfire_digests type, const unsigned char* digest, size_t length) { |
409 | if (!digest || !length) { | |
5e8dfbeb MT |
410 | errno = EINVAL; |
411 | return 1; | |
412 | } | |
413 | ||
65131b30 MT |
414 | // Find any existing digests of this type |
415 | struct pakfire_file_digest* d = pakfire_file_find_digest(file, type); | |
416 | ||
417 | // If there is no digest, we will try finding a new one | |
418 | if (!d) | |
419 | d = pakfire_file_find_digest(file, PAKFIRE_DIGEST_NONE); | |
420 | ||
421 | // If we could not find a free spot, we probably run out of space | |
baf8b0ed MT |
422 | if (!d) { |
423 | errno = ENOBUFS; | |
424 | return 1; | |
425 | } | |
426 | ||
427 | // Check if the digest fits into our pre-allocated buffer space | |
428 | if (length > sizeof(d->digest)) { | |
65131b30 | 429 | errno = ENOBUFS; |
5e8dfbeb MT |
430 | return 1; |
431 | } | |
432 | ||
65131b30 MT |
433 | // Store type & length |
434 | d->type = type; | |
435 | d->length = length; | |
5e8dfbeb MT |
436 | |
437 | // Store digest | |
65131b30 | 438 | memcpy(d->digest, digest, d->length); |
5e8dfbeb MT |
439 | |
440 | return 0; | |
221cc3ce MT |
441 | } |
442 | ||
5e8dfbeb MT |
443 | PAKFIRE_EXPORT int pakfire_file_set_hexdigest(struct pakfire_file* file, |
444 | enum pakfire_digests type, const char* hexdigest) { | |
445 | const size_t digest_length = pakfire_digest_length(type); | |
446 | if (!digest_length) { | |
447 | errno = EINVAL; | |
448 | return 1; | |
ffbef232 MT |
449 | } |
450 | ||
5e8dfbeb MT |
451 | // Allocate a buffer for the binary representation of the digest |
452 | unsigned char* digest = alloca(digest_length); | |
453 | if (!digest) | |
454 | return 1; | |
455 | ||
456 | // Convert from hex to binary | |
457 | __pakfire_unhexlify(digest, digest_length, hexdigest); | |
458 | ||
65131b30 | 459 | return pakfire_file_set_digest(file, type, digest, digest_length); |
221cc3ce | 460 | } |
3fca5032 | 461 | |
5803b5f6 | 462 | static int pakfire_file_levels(struct pakfire_file* file) { |
3fca5032 MT |
463 | if (!*file->path) |
464 | return 0; | |
465 | ||
466 | int levels = 0; | |
467 | ||
468 | for (char* p = file->path; *p; p++) { | |
469 | if (*p == '/') | |
470 | levels++; | |
471 | } | |
472 | ||
473 | return levels; | |
474 | } | |
475 | ||
d5a13ade MT |
476 | FILE* pakfire_file_open(struct pakfire_file* file) { |
477 | FILE* f = fopen(file->abspath, "r"); | |
478 | if (!f) | |
479 | ERROR(file->pakfire, "Could not open %s: %m\n", file->abspath); | |
480 | ||
481 | return f; | |
482 | } | |
483 | ||
ac71886a | 484 | int pakfire_file_remove(struct pakfire_file* file) { |
3fca5032 MT |
485 | if (!*file->abspath) { |
486 | errno = EINVAL; | |
487 | return 1; | |
488 | } | |
489 | ||
490 | DEBUG(file->pakfire, "Removing %s...\n", file->path); | |
491 | ||
492 | int r = remove(file->abspath); | |
493 | if (r) { | |
da89e02f MT |
494 | switch (errno) { |
495 | // Ignore when we could not remove directories | |
496 | case ENOTEMPTY: | |
497 | return 0; | |
498 | ||
499 | // Ignore if the file didn't exist | |
500 | case ENOENT: | |
501 | return 0; | |
502 | ||
503 | default: | |
504 | break; | |
505 | } | |
3fca5032 | 506 | |
b1772bfb | 507 | ERROR(file->pakfire, "Could not remove %s (%s): %m\n", file->path, file->abspath); |
3fca5032 MT |
508 | } |
509 | ||
ac71886a MT |
510 | return r; |
511 | } | |
512 | ||
513 | /* | |
514 | This function tries to remove the file after it has been packaged. | |
515 | ||
516 | It will try to delete any parent directories as well and ignore if directories | |
517 | cannot be deleted because they might contain other files | |
518 | */ | |
519 | int pakfire_file_cleanup(struct pakfire_file* file) { | |
520 | char path[PATH_MAX]; | |
521 | ||
522 | // Try removing the file | |
523 | int r = pakfire_file_remove(file); | |
524 | if (r) | |
525 | return r; | |
526 | ||
3fca5032 MT |
527 | // Create a working copy of abspath |
528 | r = pakfire_string_set(path, file->abspath); | |
529 | if (r < 0) | |
530 | return r; | |
531 | ||
532 | // See how many levels this file has | |
533 | int levels = pakfire_file_levels(file); | |
534 | ||
535 | // Walk all the way up and remove all parent directories if possible | |
536 | while (--levels) { | |
537 | dirname(path); | |
538 | ||
539 | // Break if path is suddenly empty | |
540 | if (!*path) | |
541 | break; | |
542 | ||
543 | r = rmdir(path); | |
544 | if (r) { | |
545 | if (errno == ENOTEMPTY) | |
546 | return 0; | |
547 | ||
548 | return r; | |
549 | } | |
550 | } | |
551 | ||
a942166c | 552 | return 0; |
3fca5032 | 553 | } |