1 /** @file findfile_unix.c Win32-style native file finding.
2  *
3  * @author Copyright &copy; 2004-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @author Copyright &copy; 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