1 | /* Copyright (c) 2000, 2013, Oracle and/or its affiliates. |
2 | Copyright (c) 2012, 2013, Monty Program Ab. |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of the GNU General Public License as published by |
6 | the Free Software Foundation; version 2 of the License. |
7 | |
8 | This program is distributed in the hope that it will be useful, |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | GNU General Public License for more details. |
12 | |
13 | You should have received a copy of the GNU General Public License |
14 | along with this program; if not, write to the Free Software |
15 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | */ |
17 | |
18 | #include "mysys_priv.h" |
19 | #include <m_string.h> |
20 | #ifdef HAVE_PWD_H |
21 | #include <pwd.h> |
22 | #endif |
23 | |
24 | static char * expand_tilde(char **path); |
25 | |
26 | /* Pack a dirname ; Changes HOME to ~/ and current dev to ./ */ |
27 | /* from is a dirname (from dirname() ?) ending with FN_LIBCHAR */ |
28 | /* to may be == from */ |
29 | |
30 | void pack_dirname(char * to, const char *from) |
31 | { |
32 | int cwd_err; |
33 | size_t d_length,length,UNINIT_VAR(buff_length); |
34 | char * start; |
35 | char buff[FN_REFLEN + 1]; |
36 | DBUG_ENTER("pack_dirname" ); |
37 | |
38 | (void) intern_filename(to,from); /* Change to intern name */ |
39 | |
40 | #ifdef FN_DEVCHAR |
41 | if ((start=strrchr(to,FN_DEVCHAR)) != 0) /* Skip device part */ |
42 | start++; |
43 | else |
44 | #endif |
45 | start=to; |
46 | |
47 | if (!(cwd_err= my_getwd(buff,FN_REFLEN,MYF(0)))) |
48 | { |
49 | buff_length= strlen(buff); |
50 | d_length= (size_t) (start-to); |
51 | if ((start == to || |
52 | (buff_length == d_length && !memcmp(buff,start,d_length))) && |
53 | *start != FN_LIBCHAR && *start) |
54 | { /* Put current dir before */ |
55 | bchange((uchar*) to, d_length, (uchar*) buff, buff_length, strlen(to)+1); |
56 | } |
57 | } |
58 | |
59 | if ((d_length= cleanup_dirname(to,to)) != 0) |
60 | { |
61 | length=0; |
62 | if (home_dir) |
63 | { |
64 | length= strlen(home_dir); |
65 | if (home_dir[length-1] == FN_LIBCHAR) |
66 | length--; /* Don't test last '/' */ |
67 | } |
68 | if (length > 1 && length < d_length) |
69 | { /* test if /xx/yy -> ~/yy */ |
70 | if (memcmp(to,home_dir,length) == 0 && to[length] == FN_LIBCHAR) |
71 | { |
72 | to[0]=FN_HOMELIB; /* Filename begins with ~ */ |
73 | (void) strmov_overlapp(to+1,to+length); |
74 | } |
75 | } |
76 | if (! cwd_err) |
77 | { /* Test if cwd is ~/... */ |
78 | if (length > 1 && length < buff_length) |
79 | { |
80 | if (memcmp(buff,home_dir,length) == 0 && buff[length] == FN_LIBCHAR) |
81 | { |
82 | buff[0]=FN_HOMELIB; |
83 | (void) strmov_overlapp(buff+1,buff+length); |
84 | } |
85 | } |
86 | if (is_prefix(to,buff)) |
87 | { |
88 | length= strlen(buff); |
89 | if (to[length]) |
90 | (void) strmov_overlapp(to,to+length); /* Remove everything before */ |
91 | else |
92 | { |
93 | to[0]= FN_CURLIB; /* Put ./ instead of cwd */ |
94 | to[1]= FN_LIBCHAR; |
95 | to[2]= '\0'; |
96 | } |
97 | } |
98 | } |
99 | } |
100 | DBUG_PRINT("exit" ,("to: '%s'" ,to)); |
101 | DBUG_VOID_RETURN; |
102 | } /* pack_dirname */ |
103 | |
104 | |
105 | /* |
106 | remove unwanted chars from dirname |
107 | |
108 | SYNOPSIS |
109 | cleanup_dirname() |
110 | to Store result here |
111 | from Dirname to fix. May be same as to |
112 | |
113 | IMPLEMENTATION |
114 | "/../" removes prev dir |
115 | "/~/" removes all before ~ |
116 | //" is same as "/", except on Win32 at start of a file |
117 | "/./" is removed |
118 | Unpacks home_dir if "~/.." used |
119 | Unpacks current dir if if "./.." used |
120 | |
121 | RETURN |
122 | # length of new name |
123 | */ |
124 | |
125 | size_t cleanup_dirname(register char *to, const char *from) |
126 | { |
127 | reg5 size_t length; |
128 | reg2 char * pos; |
129 | reg3 char * from_ptr; |
130 | reg4 char * start; |
131 | char parent[5], /* for "FN_PARENTDIR" */ |
132 | buff[FN_REFLEN + 1],*end_parentdir; |
133 | #ifdef BACKSLASH_MBTAIL |
134 | CHARSET_INFO *fs= fs_character_set(); |
135 | #endif |
136 | DBUG_ENTER("cleanup_dirname" ); |
137 | DBUG_PRINT("enter" ,("from: '%s'" ,from)); |
138 | |
139 | start=buff; |
140 | from_ptr=(char *) from; |
141 | #ifdef FN_DEVCHAR |
142 | if ((pos=strrchr(from_ptr,FN_DEVCHAR)) != 0) |
143 | { /* Skip device part */ |
144 | length=(size_t) (pos-from_ptr)+1; |
145 | start=strnmov(buff,from_ptr,length); from_ptr+=length; |
146 | } |
147 | #endif |
148 | |
149 | parent[0]=FN_LIBCHAR; |
150 | length=(size_t) (strmov(parent+1,FN_PARENTDIR)-parent); |
151 | for (pos=start ; (*pos= *from_ptr++) != 0 ; pos++) |
152 | { |
153 | #ifdef BACKSLASH_MBTAIL |
154 | uint l; |
155 | if (use_mb(fs) && (l= my_ismbchar(fs, from_ptr - 1, from_ptr + 2))) |
156 | { |
157 | for (l-- ; l ; *++pos= *from_ptr++, l--); |
158 | start= pos + 1; /* Don't look inside multi-byte char */ |
159 | continue; |
160 | } |
161 | #endif |
162 | if (*pos == '/') |
163 | *pos = FN_LIBCHAR; |
164 | if (*pos == FN_LIBCHAR) |
165 | { |
166 | if ((size_t) (pos-start) > length && memcmp(pos-length,parent,length) == 0) |
167 | { /* If .../../; skip prev */ |
168 | pos-=length; |
169 | if (pos != start) |
170 | { /* not /../ */ |
171 | pos--; |
172 | if (*pos == FN_HOMELIB && (pos == start || pos[-1] == FN_LIBCHAR)) |
173 | { |
174 | if (!home_dir) |
175 | { |
176 | pos+=length+1; /* Don't unpack ~/.. */ |
177 | continue; |
178 | } |
179 | pos=strmov(buff,home_dir)-1; /* Unpacks ~/.. */ |
180 | if (*pos == FN_LIBCHAR) |
181 | pos--; /* home ended with '/' */ |
182 | } |
183 | if (*pos == FN_CURLIB && (pos == start || pos[-1] == FN_LIBCHAR)) |
184 | { |
185 | if (my_getwd(curr_dir,FN_REFLEN,MYF(0))) |
186 | { |
187 | pos+=length+1; /* Don't unpack ./.. */ |
188 | continue; |
189 | } |
190 | pos=strmov(buff,curr_dir)-1; /* Unpacks ./.. */ |
191 | if (*pos == FN_LIBCHAR) |
192 | pos--; /* home ended with '/' */ |
193 | } |
194 | end_parentdir=pos; |
195 | while (pos >= start && *pos != FN_LIBCHAR) /* remove prev dir */ |
196 | pos--; |
197 | if (pos[1] == FN_HOMELIB || |
198 | (pos >= start && memcmp(pos, parent, length) == 0)) |
199 | { /* Don't remove ~user/ */ |
200 | pos=strmov(end_parentdir+1,parent); |
201 | *pos=FN_LIBCHAR; |
202 | continue; |
203 | } |
204 | } |
205 | } |
206 | else if ((size_t) (pos-start) == length-1 && |
207 | !memcmp(start,parent+1,length-1)) |
208 | start=pos; /* Starts with "../" */ |
209 | else if (pos-start > 0 && pos[-1] == FN_LIBCHAR) |
210 | { |
211 | #ifdef FN_NETWORK_DRIVES |
212 | if (pos-start != 1) |
213 | #endif |
214 | pos--; /* Remove dupplicate '/' */ |
215 | } |
216 | else if (pos-start > 1 && pos[-1] == FN_CURLIB && pos[-2] == FN_LIBCHAR) |
217 | pos-=2; /* Skip /./ */ |
218 | } |
219 | } |
220 | (void) strmov(to,buff); |
221 | DBUG_PRINT("exit" ,("to: '%s'" ,to)); |
222 | DBUG_RETURN((size_t) (pos-buff)); |
223 | } /* cleanup_dirname */ |
224 | |
225 | |
226 | /* |
227 | On system where you don't have symbolic links, the following |
228 | code will allow you to create a file: |
229 | directory-name.sym that should contain the real path |
230 | to the directory. This will be used if the directory name |
231 | doesn't exists |
232 | */ |
233 | |
234 | |
235 | my_bool my_use_symdir=0; /* Set this if you want to use symdirs */ |
236 | |
237 | #ifdef USE_SYMDIR |
238 | void symdirget(char *dir) |
239 | { |
240 | char buff[FN_REFLEN + 1]; |
241 | char *pos=strend(dir); |
242 | if (dir[0] && pos[-1] != FN_DEVCHAR && my_access(dir, F_OK)) |
243 | { |
244 | File file; |
245 | size_t length; |
246 | char temp= *(--pos); /* May be "/" or "\" */ |
247 | strmov(pos,".sym" ); |
248 | file= my_open(dir, O_RDONLY, MYF(0)); |
249 | *pos++=temp; *pos=0; /* Restore old filename */ |
250 | if (file >= 0) |
251 | { |
252 | if ((length= my_read(file, buff, sizeof(buff) - 1, MYF(0))) > 0) |
253 | { |
254 | for (pos= buff + length ; |
255 | pos > buff && (iscntrl(pos[-1]) || isspace(pos[-1])) ; |
256 | pos --); |
257 | |
258 | /* Ensure that the symlink ends with the directory symbol */ |
259 | if (pos == buff || pos[-1] != FN_LIBCHAR) |
260 | *pos++=FN_LIBCHAR; |
261 | |
262 | strmake(dir,buff, (size_t) (pos-buff)); |
263 | } |
264 | my_close(file, MYF(0)); |
265 | } |
266 | } |
267 | } |
268 | #endif /* USE_SYMDIR */ |
269 | |
270 | |
271 | /** |
272 | Convert a directory name to a format which can be compared as strings |
273 | |
274 | @param to result buffer, FN_REFLEN chars in length; may be == from |
275 | @param from 'packed' directory name, in whatever format |
276 | @returns size of the normalized name |
277 | |
278 | @details |
279 | - Ensures that last char is FN_LIBCHAR, unless it is FN_DEVCHAR |
280 | - Uses cleanup_dirname |
281 | |
282 | It does *not* expand ~/ (although, see cleanup_dirname). Nor does it do |
283 | any case folding. All case-insensitive normalization should be done by |
284 | the caller. |
285 | */ |
286 | |
287 | size_t normalize_dirname(char *to, const char *from) |
288 | { |
289 | size_t length; |
290 | char buff[FN_REFLEN + 1]; |
291 | DBUG_ENTER("normalize_dirname" ); |
292 | |
293 | /* |
294 | Despite the name, this actually converts the name to the system's |
295 | format (TODO: name this properly). |
296 | */ |
297 | (void) intern_filename(buff, from); |
298 | length= strlen(buff); /* Fix that '/' is last */ |
299 | if (length && |
300 | #ifdef FN_DEVCHAR |
301 | buff[length - 1] != FN_DEVCHAR && |
302 | #endif |
303 | buff[length - 1] != FN_LIBCHAR && buff[length - 1] != '/') |
304 | { |
305 | /* we need reserve 2 bytes for the trailing slash and the zero */ |
306 | if (length >= sizeof (buff) - 1) |
307 | length= sizeof (buff) - 2; |
308 | buff[length]= FN_LIBCHAR; |
309 | buff[length + 1]= '\0'; |
310 | } |
311 | |
312 | length=cleanup_dirname(to, buff); |
313 | |
314 | DBUG_RETURN(length); |
315 | } |
316 | |
317 | |
318 | /** |
319 | Fixes a directory name so that can be used by open() |
320 | |
321 | @param to Result buffer, FN_REFLEN characters. May be == from |
322 | @param from 'Packed' directory name (may contain ~) |
323 | |
324 | @details |
325 | - Uses normalize_dirname() |
326 | - Expands ~/... to home_dir/... |
327 | - Resolves MySQL's fake "foo.sym" symbolic directory names (if USE_SYMDIR) |
328 | - Changes a UNIX filename to system filename (replaces / with \ on windows) |
329 | |
330 | @returns |
331 | Length of new directory name (= length of to) |
332 | */ |
333 | |
334 | size_t unpack_dirname(char * to, const char *from) |
335 | { |
336 | size_t length, h_length; |
337 | char buff[FN_REFLEN+1+4],*suffix,*tilde_expansion; |
338 | DBUG_ENTER("unpack_dirname" ); |
339 | |
340 | length= normalize_dirname(buff, from); |
341 | |
342 | if (buff[0] == FN_HOMELIB) |
343 | { |
344 | suffix=buff+1; tilde_expansion=expand_tilde(&suffix); |
345 | if (tilde_expansion) |
346 | { |
347 | length-= (size_t) (suffix-buff)-1; |
348 | if (length+(h_length= strlen(tilde_expansion)) <= FN_REFLEN) |
349 | { |
350 | if ((h_length > 0) && (tilde_expansion[h_length-1] == FN_LIBCHAR)) |
351 | h_length--; |
352 | if (buff+h_length < suffix) |
353 | bmove(buff+h_length,suffix,length); |
354 | else |
355 | bmove_upp((uchar*) buff+h_length+length, (uchar*) suffix+length, length); |
356 | bmove(buff,tilde_expansion,h_length); |
357 | } |
358 | } |
359 | } |
360 | #ifdef USE_SYMDIR |
361 | if (my_use_symdir) |
362 | symdirget(buff); |
363 | #endif |
364 | DBUG_RETURN(system_filename(to,buff)); /* Fix for open */ |
365 | } /* unpack_dirname */ |
366 | |
367 | |
368 | /* Expand tilde to home or user-directory */ |
369 | /* Path is reset to point at FN_LIBCHAR after ~xxx */ |
370 | |
371 | static char * expand_tilde(char **path) |
372 | { |
373 | if (path[0][0] == FN_LIBCHAR) |
374 | return home_dir; /* ~/ expanded to home */ |
375 | #ifdef HAVE_GETPWNAM |
376 | { |
377 | char *str,save; |
378 | struct passwd *user_entry; |
379 | |
380 | if (!(str=strchr(*path,FN_LIBCHAR))) |
381 | str=strend(*path); |
382 | save= *str; *str= '\0'; |
383 | user_entry=getpwnam(*path); |
384 | *str=save; |
385 | endpwent(); |
386 | if (user_entry) |
387 | { |
388 | *path=str; |
389 | return user_entry->pw_dir; |
390 | } |
391 | } |
392 | #endif |
393 | return (char *) 0; |
394 | } |
395 | |
396 | |
397 | /* |
398 | Fix filename so it can be used by open, create |
399 | |
400 | SYNOPSIS |
401 | unpack_filename() |
402 | to Store result here. Must be at least of size FN_REFLEN. |
403 | from Filename in unix format (with ~) |
404 | |
405 | RETURN |
406 | # length of to |
407 | |
408 | NOTES |
409 | to may be == from |
410 | ~ will only be expanded if total length < FN_REFLEN |
411 | */ |
412 | |
413 | |
414 | size_t unpack_filename(char * to, const char *from) |
415 | { |
416 | size_t length, n_length, buff_length; |
417 | char buff[FN_REFLEN + 1]; |
418 | DBUG_ENTER("unpack_filename" ); |
419 | |
420 | length=dirname_part(buff, from, &buff_length);/* copy & convert dirname */ |
421 | n_length=unpack_dirname(buff,buff); |
422 | if (n_length+strlen(from+length) < FN_REFLEN) |
423 | { |
424 | (void) strmov(buff+n_length,from+length); |
425 | length= system_filename(to,buff); /* Fix to usably filename */ |
426 | } |
427 | else |
428 | length= system_filename(to,from); /* Fix to usably filename */ |
429 | DBUG_RETURN(length); |
430 | } /* unpack_filename */ |
431 | |
432 | |
433 | /* Convert filename (unix standard) to system standard */ |
434 | /* Used before system command's like open(), create() .. */ |
435 | /* Returns used length of to; total length should be FN_REFLEN */ |
436 | |
437 | size_t system_filename(char *to, const char *from) |
438 | { |
439 | return (size_t) (strmake(to,from,FN_REFLEN-1)-to); |
440 | } |
441 | |
442 | /* Fix a filename to intern (UNIX format) */ |
443 | |
444 | char *intern_filename(char *to, const char *from) |
445 | { |
446 | size_t length, to_length; |
447 | char buff[FN_REFLEN + 1]; |
448 | if (from == to) |
449 | { /* Dirname may destroy from */ |
450 | (void) strnmov(buff, from, FN_REFLEN); |
451 | from=buff; |
452 | } |
453 | length= dirname_part(to, from, &to_length); /* Copy dirname & fix chars */ |
454 | (void) strnmov(to + to_length, from + length, FN_REFLEN - to_length); |
455 | return (to); |
456 | } /* intern_filename */ |
457 | |