1 /* ______ ___ ___
2 * /\ _ \ /\_ \ /\_ \
3 * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___
4 * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\
5 * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \
6 * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7 * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8 * /\____/
9 * \_/__/
10 *
11 * Filesystem path routines.
12 *
13 * By Thomas Fjellstrom.
14 *
15 * See LICENSE.txt for copyright information.
16 */
17
18 /* Title: Filesystem path routines
19 */
20
21 #include <stdio.h>
22 #include "allegro5/allegro.h"
23 #include "allegro5/internal/aintern.h"
24 #include "allegro5/internal/aintern_vector.h"
25 #include "allegro5/internal/aintern_path.h"
26
27
28 /* get_segment:
29 * Return the i'th directory component of a path.
30 */
get_segment(const ALLEGRO_PATH * path,unsigned i)31 static ALLEGRO_USTR *get_segment(const ALLEGRO_PATH *path, unsigned i)
32 {
33 ALLEGRO_USTR **seg = _al_vector_ref(&path->segments, i);
34 return *seg;
35 }
36
37
38 /* get_segment_cstr:
39 * Return the i'th directory component of a path as a C string.
40 */
get_segment_cstr(const ALLEGRO_PATH * path,unsigned i)41 static const char *get_segment_cstr(const ALLEGRO_PATH *path, unsigned i)
42 {
43 return al_cstr(get_segment(path, i));
44 }
45
46
47 /* replace_backslashes:
48 * Replace backslashes by slashes.
49 */
replace_backslashes(ALLEGRO_USTR * path)50 static void replace_backslashes(ALLEGRO_USTR *path)
51 {
52 al_ustr_find_replace_cstr(path, 0, "\\", "/");
53 }
54
55
56 /* parse_path_string:
57 *
58 * Parse a path string according to the following grammar. The last
59 * component, if it is not followed by a directory separator, is interpreted
60 * as the filename component, unless it is "." or "..".
61 *
62 * GRAMMAR
63 *
64 * path ::= "//" c+ "/" nonlast [Windows only]
65 * | c ":" nonlast [Windows only]
66 * | nonlast
67 *
68 * nonlast ::= c* "/" nonlast
69 * | last
70 *
71 * last ::= "."
72 * | ".."
73 * | filename
74 *
75 * filename ::= c* [but not "." and ".."]
76 *
77 * c ::= any character but '/'
78 */
parse_path_string(const ALLEGRO_USTR * str,ALLEGRO_PATH * path)79 static bool parse_path_string(const ALLEGRO_USTR *str, ALLEGRO_PATH *path)
80 {
81 ALLEGRO_USTR_INFO dot_info;
82 ALLEGRO_USTR_INFO dotdot_info;
83 const ALLEGRO_USTR * dot = al_ref_cstr(&dot_info, ".");
84 const ALLEGRO_USTR * dotdot = al_ref_cstr(&dotdot_info, "..");
85
86 ALLEGRO_USTR *piece = al_ustr_new("");
87 int pos = 0;
88 bool on_windows;
89
90 /* We compile the drive handling code on non-Windows platforms to prevent
91 * it becoming broken.
92 */
93 #ifdef ALLEGRO_WINDOWS
94 on_windows = true;
95 #else
96 on_windows = false;
97 #endif
98 if (on_windows) {
99 /* UNC \\server\share name */
100 if (al_ustr_has_prefix_cstr(str, "//")) {
101 int slash = al_ustr_find_chr(str, 2, '/');
102 if (slash == -1 || slash == 2) {
103 /* Missing slash or server component is empty. */
104 goto Error;
105 }
106 al_ustr_assign_substr(path->drive, str, pos, slash);
107 // Note: The slash will be parsed again, so we end up with
108 // "//server/share" and not "//servershare"!
109 pos = slash;
110 }
111 else {
112 /* Drive letter. */
113 int colon = al_ustr_offset(str, 1);
114 if (colon > -1 && al_ustr_get(str, colon) == ':') {
115 /* Include the colon in the drive string. */
116 al_ustr_assign_substr(path->drive, str, 0, colon + 1);
117 pos = colon + 1;
118 }
119 }
120 }
121
122 for (;;) {
123 int slash = al_ustr_find_chr(str, pos, '/');
124
125 if (slash == -1) {
126 /* Last component. */
127 al_ustr_assign_substr(piece, str, pos, al_ustr_size(str));
128 if (al_ustr_equal(piece, dot) || al_ustr_equal(piece, dotdot)) {
129 al_append_path_component(path, al_cstr(piece));
130 }
131 else {
132 /* This might be an empty string, but that's okay. */
133 al_ustr_assign(path->filename, piece);
134 }
135 break;
136 }
137
138 /* Non-last component. */
139 al_ustr_assign_substr(piece, str, pos, slash);
140 al_append_path_component(path, al_cstr(piece));
141 pos = slash + 1;
142 }
143
144 al_ustr_free(piece);
145 return true;
146
147 Error:
148
149 al_ustr_free(piece);
150 return false;
151 }
152
153
154 /* Function: al_create_path
155 */
al_create_path(const char * str)156 ALLEGRO_PATH *al_create_path(const char *str)
157 {
158 ALLEGRO_PATH *path;
159
160 path = al_malloc(sizeof(ALLEGRO_PATH));
161 if (!path)
162 return NULL;
163
164 path->drive = al_ustr_new("");
165 path->filename = al_ustr_new("");
166 _al_vector_init(&path->segments, sizeof(ALLEGRO_USTR *));
167 path->basename = al_ustr_new("");
168 path->full_string = al_ustr_new("");
169
170 if (str != NULL) {
171 ALLEGRO_USTR *copy = al_ustr_new(str);
172 replace_backslashes(copy);
173
174 if (!parse_path_string(copy, path)) {
175 al_destroy_path(path);
176 path = NULL;
177 }
178
179 al_ustr_free(copy);
180 }
181
182 return path;
183 }
184
185
186 /* Function: al_create_path_for_directory
187 */
al_create_path_for_directory(const char * str)188 ALLEGRO_PATH *al_create_path_for_directory(const char *str)
189 {
190 ALLEGRO_PATH *path = al_create_path(str);
191 if (al_ustr_length(path->filename)) {
192 ALLEGRO_USTR *last = path->filename;
193 path->filename = al_ustr_new("");
194 al_append_path_component(path, al_cstr(last));
195 al_ustr_free(last);
196 }
197 return path;
198 }
199
200
201 /* Function: al_clone_path
202 */
al_clone_path(const ALLEGRO_PATH * path)203 ALLEGRO_PATH *al_clone_path(const ALLEGRO_PATH *path)
204 {
205 ALLEGRO_PATH *clone;
206 unsigned int i;
207
208 ASSERT(path);
209
210 clone = al_create_path(NULL);
211 if (!clone) {
212 return NULL;
213 }
214
215 al_ustr_assign(clone->drive, path->drive);
216 al_ustr_assign(clone->filename, path->filename);
217
218 for (i = 0; i < _al_vector_size(&path->segments); i++) {
219 ALLEGRO_USTR **slot = _al_vector_alloc_back(&clone->segments);
220 (*slot) = al_ustr_dup(get_segment(path, i));
221 }
222
223 return clone;
224 }
225
226
227 /* Function: al_get_path_num_components
228 */
al_get_path_num_components(const ALLEGRO_PATH * path)229 int al_get_path_num_components(const ALLEGRO_PATH *path)
230 {
231 ASSERT(path);
232
233 return _al_vector_size(&path->segments);
234 }
235
236
237 /* Function: al_get_path_component
238 */
al_get_path_component(const ALLEGRO_PATH * path,int i)239 const char *al_get_path_component(const ALLEGRO_PATH *path, int i)
240 {
241 ASSERT(path);
242 ASSERT(i < (int)_al_vector_size(&path->segments));
243
244 if (i < 0)
245 i = _al_vector_size(&path->segments) + i;
246
247 ASSERT(i >= 0);
248
249 return get_segment_cstr(path, i);
250 }
251
252
253 /* Function: al_replace_path_component
254 */
al_replace_path_component(ALLEGRO_PATH * path,int i,const char * s)255 void al_replace_path_component(ALLEGRO_PATH *path, int i, const char *s)
256 {
257 ASSERT(path);
258 ASSERT(s);
259 ASSERT(i < (int)_al_vector_size(&path->segments));
260
261 if (i < 0)
262 i = _al_vector_size(&path->segments) + i;
263
264 ASSERT(i >= 0);
265
266 al_ustr_assign_cstr(get_segment(path, i), s);
267 }
268
269
270 /* Function: al_remove_path_component
271 */
al_remove_path_component(ALLEGRO_PATH * path,int i)272 void al_remove_path_component(ALLEGRO_PATH *path, int i)
273 {
274 ASSERT(path);
275 ASSERT(i < (int)_al_vector_size(&path->segments));
276
277 if (i < 0)
278 i = _al_vector_size(&path->segments) + i;
279
280 ASSERT(i >= 0);
281
282 al_ustr_free(get_segment(path, i));
283 _al_vector_delete_at(&path->segments, i);
284 }
285
286
287 /* Function: al_insert_path_component
288 */
al_insert_path_component(ALLEGRO_PATH * path,int i,const char * s)289 void al_insert_path_component(ALLEGRO_PATH *path, int i, const char *s)
290 {
291 ALLEGRO_USTR **slot;
292 ASSERT(path);
293 ASSERT(i <= (int)_al_vector_size(&path->segments));
294
295 if (i < 0)
296 i = _al_vector_size(&path->segments) + i;
297
298 ASSERT(i >= 0);
299
300 slot = _al_vector_alloc_mid(&path->segments, i);
301 (*slot) = al_ustr_new(s);
302 }
303
304
305 /* Function: al_get_path_tail
306 */
al_get_path_tail(const ALLEGRO_PATH * path)307 const char *al_get_path_tail(const ALLEGRO_PATH *path)
308 {
309 ASSERT(path);
310
311 if (al_get_path_num_components(path) == 0)
312 return NULL;
313
314 return al_get_path_component(path, -1);
315 }
316
317
318 /* Function: al_drop_path_tail
319 */
al_drop_path_tail(ALLEGRO_PATH * path)320 void al_drop_path_tail(ALLEGRO_PATH *path)
321 {
322 if (al_get_path_num_components(path) > 0) {
323 al_remove_path_component(path, -1);
324 }
325 }
326
327
328 /* Function: al_append_path_component
329 */
al_append_path_component(ALLEGRO_PATH * path,const char * s)330 void al_append_path_component(ALLEGRO_PATH *path, const char *s)
331 {
332 ALLEGRO_USTR **slot = _al_vector_alloc_back(&path->segments);
333 (*slot) = al_ustr_new(s);
334 }
335
336
path_is_absolute(const ALLEGRO_PATH * path)337 static bool path_is_absolute(const ALLEGRO_PATH *path)
338 {
339 /* If the first segment is empty, we have an absolute path. */
340 return (_al_vector_size(&path->segments) > 0)
341 && (al_ustr_size(get_segment(path, 0)) == 0);
342 }
343
344
345 /* Function: al_join_paths
346 */
al_join_paths(ALLEGRO_PATH * path,const ALLEGRO_PATH * tail)347 bool al_join_paths(ALLEGRO_PATH *path, const ALLEGRO_PATH *tail)
348 {
349 unsigned i;
350 ASSERT(path);
351 ASSERT(tail);
352
353 /* Don't bother concating if the tail is an absolute path. */
354 if (path_is_absolute(tail)) {
355 return false;
356 }
357
358 /* We ignore tail->drive. The other option is to do nothing if tail
359 * contains a drive letter.
360 */
361
362 al_ustr_assign(path->filename, tail->filename);
363
364 for (i = 0; i < _al_vector_size(&tail->segments); i++) {
365 al_append_path_component(path, get_segment_cstr(tail, i));
366 }
367
368 return true;
369 }
370
371
372 /* Function: al_rebase_path
373 */
al_rebase_path(const ALLEGRO_PATH * head,ALLEGRO_PATH * tail)374 bool al_rebase_path(const ALLEGRO_PATH *head, ALLEGRO_PATH *tail)
375 {
376 unsigned i;
377 ASSERT(head);
378 ASSERT(tail);
379
380 /* Don't bother concating if the tail is an absolute path. */
381 if (path_is_absolute(tail)) {
382 return false;
383 }
384
385 al_set_path_drive(tail, al_get_path_drive(head));
386
387 for (i = 0; i < _al_vector_size(&head->segments); i++) {
388 al_insert_path_component(tail, i, get_segment_cstr(head, i));
389 }
390
391 return true;
392 }
393
394
path_to_ustr(const ALLEGRO_PATH * path,int32_t delim,ALLEGRO_USTR * str)395 static void path_to_ustr(const ALLEGRO_PATH *path, int32_t delim,
396 ALLEGRO_USTR *str)
397 {
398 unsigned i;
399
400 al_ustr_assign(str, path->drive);
401
402 for (i = 0; i < _al_vector_size(&path->segments); i++) {
403 al_ustr_append(str, get_segment(path, i));
404 al_ustr_append_chr(str, delim);
405 }
406
407 al_ustr_append(str, path->filename);
408 }
409
410
411 /* Function: al_path_cstr
412 */
al_path_cstr(const ALLEGRO_PATH * path,char delim)413 const char *al_path_cstr(const ALLEGRO_PATH *path, char delim)
414 {
415 return al_cstr(al_path_ustr(path, delim));
416 }
417
418 /* Function: al_path_ustr
419 */
al_path_ustr(const ALLEGRO_PATH * path,char delim)420 const ALLEGRO_USTR *al_path_ustr(const ALLEGRO_PATH *path, char delim)
421 {
422 path_to_ustr(path, delim, path->full_string);
423 return path->full_string;
424 }
425
426
427 /* Function: al_destroy_path
428 */
al_destroy_path(ALLEGRO_PATH * path)429 void al_destroy_path(ALLEGRO_PATH *path)
430 {
431 unsigned i;
432
433 if (!path) {
434 return;
435 }
436
437 if (path->drive) {
438 al_ustr_free(path->drive);
439 path->drive = NULL;
440 }
441
442 if (path->filename) {
443 al_ustr_free(path->filename);
444 path->filename = NULL;
445 }
446
447 for (i = 0; i < _al_vector_size(&path->segments); i++) {
448 al_ustr_free(get_segment(path, i));
449 }
450 _al_vector_free(&path->segments);
451
452 if (path->basename) {
453 al_ustr_free(path->basename);
454 path->basename = NULL;
455 }
456
457 if (path->full_string) {
458 al_ustr_free(path->full_string);
459 path->full_string = NULL;
460 }
461
462 al_free(path);
463 }
464
465
466 /* Function: al_set_path_drive
467 */
al_set_path_drive(ALLEGRO_PATH * path,const char * drive)468 void al_set_path_drive(ALLEGRO_PATH *path, const char *drive)
469 {
470 ASSERT(path);
471
472 if (drive)
473 al_ustr_assign_cstr(path->drive, drive);
474 else
475 al_ustr_truncate(path->drive, 0);
476 }
477
478
479 /* Function: al_get_path_drive
480 */
al_get_path_drive(const ALLEGRO_PATH * path)481 const char *al_get_path_drive(const ALLEGRO_PATH *path)
482 {
483 ASSERT(path);
484
485 return al_cstr(path->drive);
486 }
487
488
489 /* Function: al_set_path_filename
490 */
al_set_path_filename(ALLEGRO_PATH * path,const char * filename)491 void al_set_path_filename(ALLEGRO_PATH *path, const char *filename)
492 {
493 ASSERT(path);
494
495 if (filename)
496 al_ustr_assign_cstr(path->filename, filename);
497 else
498 al_ustr_truncate(path->filename, 0);
499 }
500
501
502 /* Function: al_get_path_filename
503 */
al_get_path_filename(const ALLEGRO_PATH * path)504 const char *al_get_path_filename(const ALLEGRO_PATH *path)
505 {
506 ASSERT(path);
507
508 return al_cstr(path->filename);
509 }
510
511
512 /* Function: al_get_path_extension
513 */
al_get_path_extension(const ALLEGRO_PATH * path)514 const char *al_get_path_extension(const ALLEGRO_PATH *path)
515 {
516 int pos;
517 ASSERT(path);
518
519 pos = al_ustr_rfind_chr(path->filename, al_ustr_size(path->filename), '.');
520 if (pos == -1)
521 pos = al_ustr_size(path->filename);
522
523 return al_cstr(path->filename) + pos; /* include dot */
524 }
525
526
527 /* Function: al_set_path_extension
528 */
al_set_path_extension(ALLEGRO_PATH * path,char const * extension)529 bool al_set_path_extension(ALLEGRO_PATH *path, char const *extension)
530 {
531 int dot;
532 ASSERT(path);
533
534 if (al_ustr_size(path->filename) == 0) {
535 return false;
536 }
537
538 dot = al_ustr_rfind_chr(path->filename, al_ustr_size(path->filename), '.');
539 if (dot >= 0) {
540 al_ustr_truncate(path->filename, dot);
541 }
542 al_ustr_append_cstr(path->filename, extension);
543 return true;
544 }
545
546
547 /* Function: al_get_path_basename
548 */
al_get_path_basename(const ALLEGRO_PATH * path)549 const char *al_get_path_basename(const ALLEGRO_PATH *path)
550 {
551 int dot;
552 ASSERT(path);
553
554 dot = al_ustr_rfind_chr(path->filename, al_ustr_size(path->filename), '.');
555 if (dot >= 0) {
556 al_ustr_assign_substr(path->basename, path->filename, 0, dot);
557 return al_cstr(path->basename);
558 }
559
560 return al_cstr(path->filename);
561 }
562
563
564 /* Function: al_make_path_canonical
565 */
al_make_path_canonical(ALLEGRO_PATH * path)566 bool al_make_path_canonical(ALLEGRO_PATH *path)
567 {
568 unsigned i;
569 ASSERT(path);
570
571 for (i = 0; i < _al_vector_size(&path->segments); ) {
572 if (strcmp(get_segment_cstr(path, i), ".") == 0)
573 al_remove_path_component(path, i);
574 else
575 i++;
576 }
577
578 /* Remove leading '..'s on absolute paths. */
579 if (_al_vector_size(&path->segments) >= 1 &&
580 al_ustr_size(get_segment(path, 0)) == 0)
581 {
582 while (_al_vector_size(&path->segments) >= 2 &&
583 strcmp(get_segment_cstr(path, 1), "..") == 0)
584 {
585 al_remove_path_component(path, 1);
586 }
587 }
588
589 return true;
590 }
591
592 /* vim: set sts=3 sw=3 et: */
593