]>
Commit | Line | Data |
---|---|---|
b24c88b3 RC |
1 | /* |
2 | * Copyright (c) 2001, Robert Collins. | |
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; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * A copy of the GNU General Public License can be found at | |
10 | * http://www.gnu.org/ | |
11 | * | |
12 | * Written by Robert Collins <rbtcollins@hotmail.com> | |
13 | * | |
14 | */ | |
15 | ||
b24c88b3 | 16 | #include "win32.h" |
45bbb3d4 CV |
17 | #include "mklink2.h" |
18 | #include "filemanip.h" | |
19 | #include "mkdir.h" | |
20 | #include "mount.h" | |
b6dab3ea CF |
21 | #include "compactos.h" |
22 | ||
94c4c95d | 23 | #include "getopt++/StringChoiceOption.h" |
45bbb3d4 | 24 | |
b24c88b3 | 25 | #include <stdlib.h> |
ca9506cc | 26 | #include <errno.h> |
b24c88b3 RC |
27 | #include <unistd.h> |
28 | ||
45bbb3d4 | 29 | #include "io_stream_cygfile.h" |
19911586 | 30 | #include "IOStreamProvider.h" |
45bbb3d4 | 31 | #include "LogSingleton.h" |
20f237b4 | 32 | #include "resource.h" |
45bbb3d4 | 33 | |
94c4c95d JT |
34 | static StringChoiceOption::StringChoices algs({ |
35 | {"xpress4k", FILE_PROVIDER_COMPRESSION_XPRESS4K}, | |
36 | {"xpress8k", FILE_PROVIDER_COMPRESSION_XPRESS8K}, | |
37 | {"xpress16k", FILE_PROVIDER_COMPRESSION_XPRESS16K}, | |
38 | {"lzx", FILE_PROVIDER_COMPRESSION_LZX}, | |
39 | }); | |
40 | ||
41 | static StringChoiceOption CompactOsOption(algs, | |
20f237b4 | 42 | '\0', "compact-os", IDS_HELPTEXT_COMPACTOS, |
94c4c95d | 43 | true, -1, FILE_PROVIDER_COMPRESSION_LZX); |
19911586 RC |
44 | |
45 | /* completely private iostream registration class */ | |
46 | class CygFileProvider : public IOStreamProvider | |
47 | { | |
48 | public: | |
a3a02820 | 49 | int exists (const std::string& path) const |
19911586 | 50 | {return io_stream_cygfile::exists(path);} |
a3a02820 | 51 | int remove (const std::string& path) const |
19911586 | 52 | {return io_stream_cygfile::remove(path);} |
a3a02820 | 53 | int mklink (const std::string& a , const std::string& b, io_stream_link_t c) const |
19911586 | 54 | {return io_stream_cygfile::mklink(a,b,c);} |
26922cd2 CV |
55 | io_stream *open (const std::string& a,const std::string& b, mode_t m) const |
56 | {return new io_stream_cygfile (a, b, m);} | |
19911586 | 57 | ~CygFileProvider (){} |
a3a02820 | 58 | int move (const std::string& a,const std::string& b) const |
19911586 | 59 | {return io_stream_cygfile::move (a, b);} |
b41c2908 CV |
60 | int mkdir_p (path_type_t isadir, const std::string& path, mode_t mode) const |
61 | {return cygmkdir_p (isadir, path, mode);} | |
19911586 RC |
62 | protected: |
63 | CygFileProvider() // no creating this | |
64 | { | |
65 | io_stream::registerProvider (theInstance, "cygfile://"); | |
66 | } | |
67 | CygFileProvider(CygFileProvider const &); // no copying | |
68 | CygFileProvider &operator=(CygFileProvider const &); // no assignment | |
69 | private: | |
70 | static CygFileProvider theInstance; | |
71 | }; | |
72 | CygFileProvider CygFileProvider::theInstance = CygFileProvider(); | |
73 | ||
b24c88b3 | 74 | |
a3a02820 | 75 | std::string io_stream_cygfile::cwd("/"); |
b6dab3ea CF |
76 | bool io_stream_cygfile::compact_os_is_available = (OSMajorVersion () >= 10); |
77 | ||
f2e49cf8 RC |
78 | // Normalise a unix style path relative to |
79 | // cwd. | |
a3a02820 MB |
80 | std::string |
81 | io_stream_cygfile::normalise (const std::string& unixpath) | |
f2e49cf8 RC |
82 | { |
83 | char *path,*tempout; | |
84 | ||
d2a3615c | 85 | if (unixpath.c_str()[0]=='/') |
f2e49cf8 RC |
86 | { |
87 | // rooted path | |
3a8630fd MB |
88 | path = new_cstr_char_array (unixpath); |
89 | tempout = new_cstr_char_array (unixpath); // paths only shrink. | |
f2e49cf8 RC |
90 | } |
91 | else | |
92 | { | |
3a8630fd MB |
93 | path = new_cstr_char_array (cwd + unixpath); |
94 | tempout = new_cstr_char_array (cwd + unixpath); //paths only shrink. | |
f2e49cf8 RC |
95 | } |
96 | ||
97 | // FIXME: handle .. depth tests to prevent / + ../foo/ stepping out | |
98 | // of the cygwin tree | |
99 | // FIXME: handle /./ sequences | |
100 | bool sawslash = false; | |
101 | char *outptr = tempout; | |
102 | for (char *ptr=path; *ptr; ++ptr) | |
103 | { | |
104 | if (*ptr == '/' && sawslash) | |
105 | --outptr; | |
106 | else if (*ptr == '/') | |
107 | sawslash=true; | |
108 | else | |
109 | sawslash=false; | |
110 | *outptr++ = *ptr; | |
111 | } | |
a3a02820 | 112 | std::string rv = tempout; |
f2e49cf8 RC |
113 | delete[] path; |
114 | delete[] tempout; | |
115 | return rv; | |
116 | } | |
117 | ||
2f0315ad CV |
118 | wchar_t * |
119 | io_stream_cygfile::w_str () | |
120 | { | |
121 | if (!wname) | |
122 | { | |
123 | wname = new wchar_t [fname.size () + 7]; | |
124 | if (wname) | |
125 | mklongpath (wname, fname.c_str (), fname.size () + 7); | |
126 | } | |
127 | return wname; | |
128 | } | |
129 | ||
b24c88b3 RC |
130 | static void |
131 | get_root_dir_now () | |
132 | { | |
3c054baf | 133 | if (get_root_dir ().size()) |
b24c88b3 | 134 | return; |
2f0315ad | 135 | read_mounts (std::string ()); |
b24c88b3 RC |
136 | } |
137 | ||
b6dab3ea CF |
138 | static bool |
139 | compactos_is_useless (const std::string& name) | |
140 | { | |
141 | const char * const p = name.c_str(); | |
142 | if (!(!strncmp (p, "/bin/", 5) || !strncmp (p, "/sbin/", 6) || !strncmp (p, "/usr/", 5))) | |
143 | return true; /* File is not in R/O tree. */ | |
144 | const size_t len = name.size(); /* >= 5 */ | |
145 | if (!strcmp (p + (len - 4), ".dll") || !strcmp (p + (len - 3), ".so")) { | |
146 | if ((len >= 5 + 11 && !strcmp (p + (len - 11), "cygwin1.dll")) | |
147 | || strstr (p + 5, "/sys-root/mingw/")) | |
148 | return false; /* Ignored by rebase. */ | |
149 | return true; /* Rebase will open file for writing which uncompresses the file. */ | |
150 | } | |
151 | if (!strcmp (p + (len - 4), ".bz2") || !strcmp (p + (len - 3), ".gz") | |
152 | || !strcmp (p + (len - 3), ".xz")) | |
153 | return true; /* File is already compressed. */ | |
154 | return false; | |
155 | } | |
156 | ||
157 | io_stream_cygfile::io_stream_cygfile (const std::string& name, const std::string& mode, mode_t perms) | |
158 | : fp(), lasterr (0), fname(), wname (NULL), compact_os_algorithm(-1) | |
b24c88b3 | 159 | { |
b24c88b3 | 160 | errno = 0; |
6dcfeb7d | 161 | if (!name.size()) |
db165a9a | 162 | { |
157dc2b8 | 163 | Log (LOG_TIMESTAMP) << "io_stream_cygfile: Bad parameters" << endLog; |
b24c88b3 | 164 | return; |
db165a9a | 165 | } |
b24c88b3 RC |
166 | |
167 | /* do this every time because the mount points may change due to fwd/back button use... | |
168 | * TODO: make this less...manual | |
169 | */ | |
170 | get_root_dir_now (); | |
3c054baf | 171 | if (!get_root_dir ().size()) |
db165a9a | 172 | { |
b24c88b3 | 173 | /* TODO: assign a errno for "no mount table :} " */ |
157dc2b8 | 174 | Log (LOG_TIMESTAMP) << "io_stream_cygfile: Error reading mounts" << endLog; |
b24c88b3 | 175 | return; |
db165a9a | 176 | } |
b24c88b3 | 177 | |
f2e49cf8 | 178 | fname = cygpath (normalise(name)); |
6dcfeb7d | 179 | if (mode.size ()) |
2f0315ad | 180 | { |
1e029da2 YS |
181 | if (fname.rfind (".exe") != std::string::npos |
182 | || fname.rfind (".dll") != std::string::npos) | |
183 | perms |= 0111; /* Make .exe and .dll always executable. */ | |
184 | fp = nt_wfopen (w_str(), mode.c_str (), perms); | |
6dcfeb7d CV |
185 | if (!fp) |
186 | { | |
187 | lasterr = errno; | |
157dc2b8 | 188 | Log (LOG_TIMESTAMP) << "io_stream_cygfile: fopen(" << name << ") failed " << errno << " " |
6dcfeb7d CV |
189 | << strerror(errno) << endLog; |
190 | } | |
b6dab3ea CF |
191 | |
192 | if (mode[0] == 'w' && compact_os_is_available && CompactOsOption >= 0 | |
193 | && !compactos_is_useless (name)) | |
194 | compact_os_algorithm = CompactOsOption; | |
2f0315ad | 195 | } |
b24c88b3 RC |
196 | } |
197 | ||
198 | io_stream_cygfile::~io_stream_cygfile () | |
199 | { | |
b24c88b3 RC |
200 | if (fp) |
201 | fclose (fp); | |
2f0315ad CV |
202 | if (wname) |
203 | delete [] wname; | |
b24c88b3 RC |
204 | } |
205 | ||
206 | /* Static members */ | |
207 | int | |
a3a02820 | 208 | io_stream_cygfile::exists (const std::string& path) |
b24c88b3 RC |
209 | { |
210 | get_root_dir_now (); | |
2f0315ad CV |
211 | if (!get_root_dir ().size()) |
212 | return 0; | |
213 | ||
1e029da2 YS |
214 | size_t len = cygpath (normalise(path)).size () + 7; |
215 | WCHAR wname[len]; | |
216 | mklongpath (wname, cygpath (normalise(path)).c_str (), len); | |
217 | DWORD attr = GetFileAttributesW (wname); | |
218 | if (attr != INVALID_FILE_ATTRIBUTES) | |
b24c88b3 RC |
219 | return 1; |
220 | return 0; | |
221 | } | |
222 | ||
223 | int | |
a3a02820 | 224 | io_stream_cygfile::remove (const std::string& path) |
b24c88b3 | 225 | { |
3c054baf | 226 | if (!path.size()) |
b24c88b3 RC |
227 | return 1; |
228 | get_root_dir_now (); | |
3c054baf | 229 | if (!get_root_dir ().size()) |
b24c88b3 RC |
230 | /* TODO: assign a errno for "no mount table :} " */ |
231 | return 1; | |
232 | ||
1e029da2 YS |
233 | size_t len = cygpath (normalise(path)).size () + 7; |
234 | WCHAR wpath[len]; | |
235 | mklongpath (wpath, cygpath (normalise(path)).c_str (), len); | |
236 | ||
237 | unsigned long w = GetFileAttributesW (wpath); | |
238 | if (w != INVALID_FILE_ATTRIBUTES && w & FILE_ATTRIBUTE_DIRECTORY) | |
b24c88b3 | 239 | { |
1e029da2 YS |
240 | len = wcslen (wpath); |
241 | WCHAR tmp[len + 10]; | |
242 | wcscpy (tmp, wpath); | |
243 | int i = 0; | |
244 | do | |
245 | { | |
246 | ++i; | |
247 | swprintf (tmp + len, L"old-%d", i); | |
b24c88b3 | 248 | } |
1e029da2 | 249 | while (GetFileAttributesW (tmp) != INVALID_FILE_ATTRIBUTES); |
781a9555 JT |
250 | Log (LOG_TIMESTAMP) << "warning: moving directory \"" << normalise(path).c_str() |
251 | << "\" out of the way." << endLog; | |
1e029da2 | 252 | MoveFileW (wpath, tmp); |
b24c88b3 | 253 | } |
a3a02820 | 254 | return io_stream::remove (std::string ("file://") + cygpath (normalise(path)).c_str()); |
b24c88b3 RC |
255 | } |
256 | ||
a5104a04 | 257 | /* Returns 0 for success */ |
b24c88b3 | 258 | int |
a3a02820 | 259 | io_stream_cygfile::mklink (const std::string& _from, const std::string& _to, |
b24c88b3 RC |
260 | io_stream_link_t linktype) |
261 | { | |
f2e49cf8 | 262 | if (!_from.size() || !_to.size()) |
b24c88b3 | 263 | return 1; |
a3a02820 MB |
264 | std::string from(normalise(_from)); |
265 | std::string to (normalise(_to)); | |
b24c88b3 RC |
266 | switch (linktype) |
267 | { | |
268 | case IO_STREAM_SYMLINK: | |
f2e49cf8 RC |
269 | // symlinks are arbitrary targets, can be anything, and are |
270 | // not subject to translation | |
d2a3615c | 271 | return mkcygsymlink (cygpath (from).c_str(), _to.c_str()); |
b24c88b3 RC |
272 | case IO_STREAM_HARDLINK: |
273 | { | |
d2e8d256 CV |
274 | /* First try to create a real hardlink. */ |
275 | if (!mkcyghardlink (cygpath (from).c_str(), cygpath (to).c_str ())) | |
276 | return 0; | |
277 | ||
278 | /* If creating a hardlink failed, we're probably on a filesystem | |
279 | which doesn't support hardlinks. If so, we also don't care for | |
280 | permissions for now. The filesystem is probably a filesystem | |
281 | which doesn't support ACLs anyway. */ | |
282 | ||
b24c88b3 RC |
283 | /* textmode alert: should we translate when linking from an binmode to a |
284 | text mode mount and vice verca? | |
285 | */ | |
26922cd2 | 286 | io_stream *in = io_stream::open (std::string ("cygfile://") + to, "rb", 0); |
b24c88b3 RC |
287 | if (!in) |
288 | { | |
157dc2b8 | 289 | Log (LOG_TIMESTAMP) << "could not open " << to |
a77b6167 | 290 | << " for reading in mklink" << endLog; |
b24c88b3 RC |
291 | return 1; |
292 | } | |
26922cd2 | 293 | io_stream *out = io_stream::open (std::string ("cygfile://") + from, "wb", 0644); |
b24c88b3 RC |
294 | if (!out) |
295 | { | |
157dc2b8 | 296 | Log (LOG_TIMESTAMP) << "could not open " << from |
a77b6167 | 297 | << " for writing in mklink" << endLog; |
b24c88b3 RC |
298 | delete in; |
299 | return 1; | |
300 | } | |
301 | ||
3c054baf | 302 | if (io_stream::copy (in, out)) |
b24c88b3 | 303 | { |
157dc2b8 | 304 | Log (LOG_TIMESTAMP) << "Failed to hardlink " << from << "->" |
a77b6167 | 305 | << to << " during file copy." << endLog; |
3c054baf RC |
306 | delete in; |
307 | delete out; | |
308 | return 1; | |
b24c88b3 RC |
309 | } |
310 | delete in; | |
311 | delete out; | |
3c054baf | 312 | return 0; |
b24c88b3 RC |
313 | } |
314 | } | |
315 | return 1; | |
316 | } | |
317 | ||
318 | ||
319 | /* virtuals */ | |
320 | ||
7c7034e8 RC |
321 | ssize_t |
322 | io_stream_cygfile::read (void *buffer, size_t len) | |
b24c88b3 | 323 | { |
4e30c4f6 CV |
324 | ssize_t ret = 0; |
325 | ||
326 | if (len && fp && !feof (fp)) | |
327 | { | |
328 | clearerr (fp); | |
329 | size_t fret = fread (buffer, 1, len, fp); | |
330 | if (fret < len && ferror (fp)) | |
331 | { | |
332 | lasterr = errno; | |
333 | ret = -1; | |
334 | } | |
335 | else | |
336 | ret = (ssize_t) fret; | |
337 | } | |
338 | return ret; | |
b24c88b3 RC |
339 | } |
340 | ||
7c7034e8 RC |
341 | ssize_t |
342 | io_stream_cygfile::write (const void *buffer, size_t len) | |
b24c88b3 | 343 | { |
4e30c4f6 CV |
344 | ssize_t ret = 0; |
345 | ||
346 | if (len && fp) | |
347 | { | |
348 | clearerr (fp); | |
349 | size_t fret = fwrite (buffer, 1, len, fp); | |
350 | if (fret < len && ferror (fp)) | |
351 | { | |
352 | lasterr = errno; | |
353 | ret = -1; | |
354 | } | |
355 | else | |
356 | ret = (ssize_t) fret; | |
357 | } | |
358 | return ret; | |
b24c88b3 RC |
359 | } |
360 | ||
7c7034e8 RC |
361 | ssize_t |
362 | io_stream_cygfile::peek (void *buffer, size_t len) | |
b24c88b3 | 363 | { |
b24c88b3 RC |
364 | if (fp) |
365 | { | |
44d31ea0 | 366 | off_t pos = ftello (fp); |
64a12e7e | 367 | ssize_t rv = read (buffer, len); |
44d31ea0 | 368 | fseeko (fp, pos, SEEK_SET); |
b24c88b3 RC |
369 | return rv; |
370 | } | |
371 | return 0; | |
372 | } | |
373 | ||
44d31ea0 | 374 | off_t |
b24c88b3 RC |
375 | io_stream_cygfile::tell () |
376 | { | |
377 | if (fp) | |
44d31ea0 CV |
378 | return ftello (fp); |
379 | ||
b24c88b3 RC |
380 | return 0; |
381 | } | |
382 | ||
44d31ea0 CV |
383 | off_t |
384 | io_stream_cygfile::seek (off_t where, io_stream_seek_t whence) | |
ca9506cc RC |
385 | { |
386 | if (fp) | |
7c7034e8 | 387 | { |
44d31ea0 | 388 | return fseeko (fp, where, (int) whence); |
7c7034e8 | 389 | } |
ca9506cc RC |
390 | lasterr = EBADF; |
391 | return -1; | |
392 | } | |
393 | ||
b24c88b3 RC |
394 | int |
395 | io_stream_cygfile::error () | |
396 | { | |
397 | if (fp) | |
398 | return ferror (fp); | |
399 | return lasterr; | |
400 | } | |
401 | ||
402 | int | |
b41c2908 | 403 | cygmkdir_p (path_type_t isadir, const std::string& _name, mode_t mode) |
b24c88b3 | 404 | { |
f2e49cf8 | 405 | if (!_name.size()) |
b24c88b3 | 406 | return 1; |
a3a02820 | 407 | std::string name(io_stream_cygfile::normalise(_name)); |
b24c88b3 | 408 | get_root_dir_now (); |
3c054baf | 409 | if (!get_root_dir ().size()) |
b24c88b3 RC |
410 | /* TODO: assign a errno for "no mount table :} " */ |
411 | return 1; | |
b41c2908 | 412 | return mkdir_p (isadir == PATH_TO_DIR ? 1 : 0, cygpath (name).c_str(), mode); |
b24c88b3 RC |
413 | } |
414 | ||
b24c88b3 | 415 | int |
26922cd2 | 416 | io_stream_cygfile::set_mtime (time_t mtime) |
b24c88b3 | 417 | { |
3c054baf | 418 | if (!fname.size()) |
b24c88b3 RC |
419 | return 1; |
420 | if (fp) | |
421 | fclose (fp); | |
422 | long long ftimev = mtime * NSPERSEC + FACTOR; | |
423 | FILETIME ftime; | |
424 | ftime.dwHighDateTime = ftimev >> 32; | |
425 | ftime.dwLowDateTime = ftimev; | |
2f0315ad | 426 | HANDLE h; |
1e029da2 YS |
427 | h = CreateFileW (w_str (), GENERIC_WRITE, |
428 | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, | |
429 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, 0); | |
21aa9a8b | 430 | if (h == INVALID_HANDLE_VALUE) |
26922cd2 | 431 | return 1; |
b6dab3ea CF |
432 | |
433 | if (compact_os_algorithm >= 0) | |
434 | { | |
435 | /* Compact OS must be applied after last WriteFile() | |
436 | and before SetFileTime(). */ | |
437 | int rc = CompactOsCompressFile (h, compact_os_algorithm); | |
438 | if (rc < 0) | |
439 | { | |
440 | DWORD err = GetLastError(); | |
441 | Log (LOG_TIMESTAMP) << "Compact OS disabled after error " << err | |
442 | << " on " << fname << endLog; | |
443 | compact_os_is_available = false; | |
444 | } | |
445 | else | |
446 | Log (LOG_BABBLE) << "Compact OS algorithm " << compact_os_algorithm | |
447 | << (rc == 0 ? " not" : "") << " applied to " << fname << endLog; | |
448 | } | |
449 | ||
21aa9a8b | 450 | SetFileTime (h, 0, 0, &ftime); |
21aa9a8b CV |
451 | CloseHandle (h); |
452 | return 0; | |
b24c88b3 | 453 | } |
7c7034e8 RC |
454 | |
455 | int | |
a3a02820 | 456 | io_stream_cygfile::move (const std::string& _from, const std::string& _to) |
7c7034e8 | 457 | { |
f2e49cf8 | 458 | if (!_from.size() || !_to.size()) |
7c7034e8 | 459 | return 1; |
a3a02820 MB |
460 | std::string from (normalise(_from)); |
461 | std::string to(normalise(_to)); | |
7c7034e8 | 462 | get_root_dir_now (); |
3c054baf | 463 | if (!get_root_dir ().size()) |
7c7034e8 RC |
464 | /* TODO: assign a errno for "no mount table :} " */ |
465 | return 1; | |
d2a3615c | 466 | return rename (cygpath (from).c_str(), cygpath (to).c_str()); |
7c7034e8 | 467 | } |
341988b9 RC |
468 | |
469 | size_t | |
470 | io_stream_cygfile::get_size () | |
471 | { | |
3c054baf | 472 | if (!fname.size() ) |
341988b9 | 473 | return 0; |
e0a4db64 | 474 | HANDLE h; |
e0a4db64 | 475 | DWORD ret = 0; |
1e029da2 YS |
476 | h = CreateFileW (w_str (), GENERIC_READ, |
477 | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, | |
478 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, 0); | |
479 | if (h != INVALID_HANDLE_VALUE) | |
e0a4db64 | 480 | { |
1e029da2 YS |
481 | ret = GetFileSize (h, NULL); |
482 | CloseHandle (h); | |
e0a4db64 RC |
483 | } |
484 | return ret; | |
341988b9 | 485 | } |