1 /** @file findfile_unix.c Win32-style native file finding.
2 *
3 * @author Copyright © 2004-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @author Copyright © 2006-2013 Daniel Swanson <danij@dengine.net>
5 *
6 * @par License
7 * GPL: http://www.gnu.org/licenses/gpl.html
8 *
9 * <small>This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2 of the License, or (at your
12 * option) any later version. This program is distributed in the hope that it
13 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15 * Public License for more details. You should have received a copy of the GNU
16 * General Public License along with this program; if not, write to the Free
17 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18 * 02110-1301 USA</small>
19 */
20
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <glob.h>
25 #include <sys/stat.h>
26
27 #include "de/findfile.h"
28 #include "de/memory.h"
29
30 #define DIR_SEP_CHAR '/'
31 #define DIR_SEP_STR "/"
32 #define FIND_ERROR -1
33
34 typedef struct fdata_s {
35 char* pattern;
36 glob_t buf;
37 int pos;
38 } fdata_t;
39
40 /**
41 * Get the info for the next file.
42 */
nextfinddata(FindData * fd)43 static int nextfinddata(FindData *fd)
44 {
45 fdata_t* data = fd->finddata;
46 char* fn, *last;
47 struct stat st;
48
49 if ((int)data->buf.gl_pathc <= data->pos)
50 {
51 // Nothing was found.
52 return FIND_ERROR;
53 }
54
55 // Nobody needs these...
56 fd->date = 0;
57 fd->time = 0;
58
59 // Get the size of the file.
60 fn = data->buf.gl_pathv[data->pos];
61 if (!stat(fn, &st))
62 fd->size = st.st_size;
63 else
64 fd->size = 0;
65
66 // Is it a directory?
67 last = fn + strlen(fn) - 1;
68 if (*last == '/')
69 {
70 // Return the name of the last directory in the path.
71 char* slash = last - 1;
72 while (*slash != '/' && slash > fn) --slash;
73 Str_Set(&fd->name, *slash == '/'? slash + 1 : slash);
74 fd->attrib = A_SUBDIR;
75 }
76 else
77 {
78 char ext[256];
79 char name[356];
80
81 _splitpath(fn, NULL, NULL, name, ext);
82 Str_Clear(&fd->name); // It may have previously been populated.
83 Str_Appendf(&fd->name, "%s%s", name, ext);
84 fd->attrib = 0;
85 }
86
87 // Advance the position.
88 data->pos++;
89 return 0;
90 }
91
FindFile_FindFirst(FindData * fd,char const * filename)92 int FindFile_FindFirst(FindData *fd, char const *filename)
93 {
94 fdata_t* data;
95
96 // Allocate a new glob struct.
97 fd->finddata = data = M_Calloc(sizeof(*data));
98 Str_InitStd(&fd->name);
99
100 // Make a copy of the pattern.
101 data->pattern = M_Malloc(strlen(filename) + 1);
102 strcpy(data->pattern, filename);
103
104 // Do the glob.
105 glob(filename, GLOB_MARK, NULL, &data->buf);
106
107 return nextfinddata(fd);
108 }
109
FindFile_FindNext(FindData * fd)110 int FindFile_FindNext(FindData *fd)
111 {
112 if (!fd->finddata)
113 return FIND_ERROR;
114 return nextfinddata(fd);
115 }
116
FindFile_Finish(FindData * fd)117 void FindFile_Finish(FindData *fd)
118 {
119 globfree(&((fdata_t*) fd->finddata)->buf);
120 M_Free(((fdata_t*) fd->finddata)->pattern);
121 Str_Free(&fd->name);
122 M_Free(fd->finddata);
123 fd->finddata = NULL;
124 }
125
126 /**
127 * Removes references to the current (.) and parent (..) directories.
128 * The given path should be an absolute path.
129 */
resolvePath(char * path)130 static void resolvePath(char* path)
131 {
132 assert(path);
133 {
134 char* ch = path;
135 char* end = path + strlen(path);
136 char* prev = path; // Assume an absolute path.
137
138 for (; *ch; ch++)
139 {
140 if (ch[0] == '/' && ch[1] == '.')
141 {
142 if (ch[2] == '/')
143 {
144 memmove(ch, ch + 2, end - ch - 1);
145 ch--;
146 }
147 else if (ch[2] == '.' && ch[3] == '/')
148 {
149 memmove(prev, ch + 3, end - ch - 2);
150 // Must restart from the beginning.
151 // This is a tad inefficient, though.
152 ch = path - 1;
153 continue;
154 }
155 }
156 if (*ch == '/')
157 prev = ch;
158 }
159 }
160 }
161
_fullpath(char * full,const char * original,int maxLen)162 char* _fullpath(char* full, const char* original, int maxLen)
163 {
164 char* cwd, *buf;
165
166 // @todo Check for '~'.
167
168 if (original[0] != DIR_SEP_CHAR) // A relative path?
169 {
170 /// @todo Check for ERANGE.
171 cwd = getcwd(NULL, 0);
172 if (!cwd) Libdeng_BadAlloc();
173 buf = (char*) M_Malloc(strlen(cwd) + 1/*DIR_SEP_CHAR*/ + strlen(original) + 1);
174 strcpy(buf, cwd);
175 strcat(buf, DIR_SEP_STR);
176 strcat(buf, original);
177 free(cwd);
178 }
179 else
180 {
181 size_t len = strlen(original);
182 buf = (char*) M_Malloc(len + 1);
183 memcpy(buf, original, len);
184 buf[len] = 0;
185 }
186
187 // Remove "."s and ".."s.
188 resolvePath(buf);
189
190 // Clear the given buffer and copy the full path there.
191 memset(full, 0, maxLen);
192 strncpy(full, buf, maxLen - 1);
193 free(buf);
194 return full;
195 }
196
strzncpy(char * dest,const char * src,int count)197 static void strzncpy(char* dest, const char* src, int count)
198 {
199 char* out = dest;
200 const char* in = src;
201
202 while (count-- > 0)
203 {
204 *out++ = *in++;
205 if (!*in)
206 break;
207 }
208 *out = 0;
209 }
210
_splitpath(const char * path,char * drive,char * dir,char * name,char * ext)211 void _splitpath(const char* path, char* drive, char* dir, char* name, char* ext)
212 {
213 char* lastPeriod, *lastSlash;
214
215 if (drive)
216 strcpy(drive, ""); // There is never a drive letter.
217
218 lastPeriod = strrchr(path, '.');
219 lastSlash = strrchr(path, '/');
220 if (lastPeriod < lastSlash)
221 lastPeriod = NULL;
222
223 if (dir)
224 {
225 if (lastSlash)
226 strzncpy(dir, path, lastSlash - path + 1);
227 else
228 strcpy(dir, "");
229 }
230
231 // The name should not include the extension.
232 if (name)
233 {
234 if (lastSlash && lastPeriod)
235 strzncpy(name, lastSlash + 1, lastPeriod - lastSlash - 1);
236 else if (lastSlash)
237 strcpy(name, lastSlash + 1);
238 else if (lastPeriod)
239 strzncpy(name, path, lastPeriod - path);
240 else
241 strcpy(name, path);
242 }
243
244 // Last period gives us the extension.
245 if (ext)
246 {
247 if (lastPeriod)
248 strcpy(ext, lastPeriod);
249 else
250 strcpy(ext, "");
251 }
252 }
253