Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(509)

Side by Side Diff: base/file_util_win.cc

Issue 141273010: Make CopyDirectory() not copy the read only bit on Windows. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: address review commens Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « base/file_util_unittest.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/file_util.h" 5 #include "base/file_util.h"
6 6
7 #include <windows.h> 7 #include <windows.h>
8 #include <psapi.h> 8 #include <psapi.h>
9 #include <shellapi.h> 9 #include <shellapi.h>
10 #include <shlobj.h> 10 #include <shlobj.h>
11 #include <time.h> 11 #include <time.h>
12 12
13 #include <algorithm> 13 #include <algorithm>
14 #include <limits> 14 #include <limits>
15 #include <string> 15 #include <string>
16 16
17 #include "base/files/file_enumerator.h"
17 #include "base/files/file_path.h" 18 #include "base/files/file_path.h"
18 #include "base/logging.h" 19 #include "base/logging.h"
19 #include "base/metrics/histogram.h" 20 #include "base/metrics/histogram.h"
20 #include "base/process/process_handle.h" 21 #include "base/process/process_handle.h"
21 #include "base/rand_util.h" 22 #include "base/rand_util.h"
22 #include "base/strings/string_number_conversions.h" 23 #include "base/strings/string_number_conversions.h"
23 #include "base/strings/string_util.h" 24 #include "base/strings/string_util.h"
24 #include "base/strings/utf_string_conversions.h" 25 #include "base/strings/utf_string_conversions.h"
25 #include "base/threading/thread_restrictions.h" 26 #include "base/threading/thread_restrictions.h"
26 #include "base/time/time.h" 27 #include "base/time/time.h"
27 #include "base/win/scoped_handle.h" 28 #include "base/win/scoped_handle.h"
28 #include "base/win/windows_version.h" 29 #include "base/win/windows_version.h"
29 30
30 namespace base { 31 namespace base {
31 32
32 namespace { 33 namespace {
33 34
34 const DWORD kFileShareAll = 35 const DWORD kFileShareAll =
35 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; 36 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
36 37
37 bool ShellCopy(const FilePath& from_path,
38 const FilePath& to_path,
39 bool recursive) {
40 // WinXP SHFileOperation doesn't like trailing separators.
41 FilePath stripped_from = from_path.StripTrailingSeparators();
42 FilePath stripped_to = to_path.StripTrailingSeparators();
43
44 ThreadRestrictions::AssertIOAllowed();
45
46 // NOTE: I suspect we could support longer paths, but that would involve
47 // analyzing all our usage of files.
48 if (stripped_from.value().length() >= MAX_PATH ||
49 stripped_to.value().length() >= MAX_PATH) {
50 return false;
51 }
52
53 // SHFILEOPSTRUCT wants the path to be terminated with two NULLs,
54 // so we have to use wcscpy because wcscpy_s writes non-NULLs
55 // into the rest of the buffer.
56 wchar_t double_terminated_path_from[MAX_PATH + 1] = {0};
57 wchar_t double_terminated_path_to[MAX_PATH + 1] = {0};
58 #pragma warning(suppress:4996) // don't complain about wcscpy deprecation
59 wcscpy(double_terminated_path_from, stripped_from.value().c_str());
60 #pragma warning(suppress:4996) // don't complain about wcscpy deprecation
61 wcscpy(double_terminated_path_to, stripped_to.value().c_str());
62
63 SHFILEOPSTRUCT file_operation = {0};
64 file_operation.wFunc = FO_COPY;
65 file_operation.pFrom = double_terminated_path_from;
66 file_operation.pTo = double_terminated_path_to;
67 file_operation.fFlags = FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION |
68 FOF_NOCONFIRMMKDIR;
69 if (!recursive)
70 file_operation.fFlags |= FOF_NORECURSION | FOF_FILESONLY;
71
72 return (SHFileOperation(&file_operation) == 0);
73 }
74
75 } // namespace 38 } // namespace
76 39
77 FilePath MakeAbsoluteFilePath(const FilePath& input) { 40 FilePath MakeAbsoluteFilePath(const FilePath& input) {
78 ThreadRestrictions::AssertIOAllowed(); 41 ThreadRestrictions::AssertIOAllowed();
79 wchar_t file_path[MAX_PATH]; 42 wchar_t file_path[MAX_PATH];
80 if (!_wfullpath(file_path, input.value().c_str(), MAX_PATH)) 43 if (!_wfullpath(file_path, input.value().c_str(), MAX_PATH))
81 return FilePath(); 44 return FilePath();
82 return FilePath(file_path); 45 return FilePath(file_path);
83 } 46 }
84 47
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
157 REPLACEFILE_IGNORE_MERGE_ERRORS, NULL, NULL)) { 120 REPLACEFILE_IGNORE_MERGE_ERRORS, NULL, NULL)) {
158 return true; 121 return true;
159 } 122 }
160 if (error) 123 if (error)
161 *error = File::OSErrorToFileError(GetLastError()); 124 *error = File::OSErrorToFileError(GetLastError());
162 return false; 125 return false;
163 } 126 }
164 127
165 bool CopyDirectory(const FilePath& from_path, const FilePath& to_path, 128 bool CopyDirectory(const FilePath& from_path, const FilePath& to_path,
166 bool recursive) { 129 bool recursive) {
130 // NOTE(maruel): Previous version of this function used to call
131 // SHFileOperation(). This used to copy the file attributes and extended
132 // attributes, OLE structured storage, NTFS file system alternate data
133 // streams, SECURITY_DESCRIPTOR. In practice, this is not what we want, we
134 // want the containing directory to propagate its SECURITY_DESCRIPTOR.
167 ThreadRestrictions::AssertIOAllowed(); 135 ThreadRestrictions::AssertIOAllowed();
168 136
169 if (recursive) 137 // NOTE: I suspect we could support longer paths, but that would involve
170 return ShellCopy(from_path, to_path, true); 138 // analyzing all our usage of files.
171 139 if (from_path.value().length() >= MAX_PATH ||
172 // The following code assumes that from path is a directory. 140 to_path.value().length() >= MAX_PATH) {
173 DCHECK(DirectoryExists(from_path)); 141 return false;
174
175 // Instead of creating a new directory, we copy the old one to include the
176 // security information of the folder as part of the copy.
177 if (!PathExists(to_path)) {
178 // Except that Vista fails to do that, and instead do a recursive copy if
179 // the target directory doesn't exist.
180 if (base::win::GetVersion() >= base::win::VERSION_VISTA)
181 CreateDirectory(to_path);
182 else
183 ShellCopy(from_path, to_path, false);
184 } 142 }
185 143
186 FilePath directory = from_path.Append(L"*.*"); 144 // This function does not properly handle destinations within the source.
187 return ShellCopy(directory, to_path, false); 145 FilePath real_to_path = to_path;
146 if (PathExists(real_to_path)) {
147 real_to_path = MakeAbsoluteFilePath(real_to_path);
148 if (real_to_path.empty())
149 return false;
150 } else {
151 real_to_path = MakeAbsoluteFilePath(real_to_path.DirName());
152 if (real_to_path.empty())
153 return false;
154 }
155 FilePath real_from_path = MakeAbsoluteFilePath(from_path);
156 if (real_from_path.empty())
157 return false;
158 if (real_to_path.value().size() >= real_from_path.value().size() &&
159 real_to_path.value().compare(0, real_from_path.value().size(),
160 real_from_path.value()) == 0)
161 return false;
162
163 int traverse_type = FileEnumerator::FILES;
164 if (recursive)
165 traverse_type |= FileEnumerator::DIRECTORIES;
166 FileEnumerator traversal(from_path, recursive, traverse_type);
167
168 if (!PathExists(from_path)) {
169 DLOG(ERROR) << "CopyDirectory() couldn't stat source directory: "
170 << from_path.value().c_str();
171 return false;
172 }
173 // TODO(maruel): This is not necessary anymore.
174 DCHECK(recursive || DirectoryExists(from_path));
175
176 FilePath current = from_path;
177 bool from_is_dir = DirectoryExists(from_path);
178 bool success = true;
179 FilePath from_path_base = from_path;
180 if (recursive && DirectoryExists(to_path)) {
181 // If the destination already exists and is a directory, then the
182 // top level of source needs to be copied.
183 from_path_base = from_path.DirName();
184 }
185
186 while (success && !current.empty()) {
187 // current is the source path, including from_path, so append
188 // the suffix after from_path to to_path to create the target_path.
189 FilePath target_path(to_path);
190 if (from_path_base != current) {
191 if (!from_path_base.AppendRelativePath(current, &target_path)) {
192 success = false;
193 break;
194 }
195 }
196
197 if (from_is_dir) {
198 if (!DirectoryExists(target_path) &&
199 !::CreateDirectory(target_path.value().c_str(), NULL)) {
200 DLOG(ERROR) << "CopyDirectory() couldn't create directory: "
201 << target_path.value().c_str();
202 success = false;
203 }
204 } else if (!internal::CopyFileUnsafe(current, target_path)) {
205 DLOG(ERROR) << "CopyDirectory() couldn't create file: "
206 << target_path.value().c_str();
207 success = false;
208 }
209
210 current = traversal.Next();
211 if (!current.empty())
212 from_is_dir = traversal.GetInfo().IsDirectory();
213 }
214
215 return success;
188 } 216 }
189 217
190 bool PathExists(const FilePath& path) { 218 bool PathExists(const FilePath& path) {
191 ThreadRestrictions::AssertIOAllowed(); 219 ThreadRestrictions::AssertIOAllowed();
192 return (GetFileAttributes(path.value().c_str()) != INVALID_FILE_ATTRIBUTES); 220 return (GetFileAttributes(path.value().c_str()) != INVALID_FILE_ATTRIBUTES);
193 } 221 }
194 222
195 bool PathIsWritable(const FilePath& path) { 223 bool PathIsWritable(const FilePath& path) {
196 ThreadRestrictions::AssertIOAllowed(); 224 ThreadRestrictions::AssertIOAllowed();
197 HANDLE dir = 225 HANDLE dir =
(...skipping 515 matching lines...) Expand 10 before | Expand all | Expand 10 after
713 if (!ret) { 741 if (!ret) {
714 // Leave a clue about what went wrong so that it can be (at least) picked 742 // Leave a clue about what went wrong so that it can be (at least) picked
715 // up by a PLOG entry. 743 // up by a PLOG entry.
716 ::SetLastError(last_error); 744 ::SetLastError(last_error);
717 } 745 }
718 746
719 return ret; 747 return ret;
720 } 748 }
721 749
722 bool CopyFileUnsafe(const FilePath& from_path, const FilePath& to_path) { 750 bool CopyFileUnsafe(const FilePath& from_path, const FilePath& to_path) {
751 // NOTE(maruel): Previous version of this function used to call CopyFile().
752 // This used to copy the file attributes and extended attributes, OLE
753 // structured storage, NTFS file system alternate data streams,
754 // SECURITY_DESCRIPTOR. In practice, this is not what we want, we want the
755 // containing directory to propagate its SECURITY_DESCRIPTOR.
723 ThreadRestrictions::AssertIOAllowed(); 756 ThreadRestrictions::AssertIOAllowed();
724 757
725 // NOTE: I suspect we could support longer paths, but that would involve 758 // NOTE: I suspect we could support longer paths, but that would involve
726 // analyzing all our usage of files. 759 // analyzing all our usage of files.
727 if (from_path.value().length() >= MAX_PATH || 760 if (from_path.value().length() >= MAX_PATH ||
728 to_path.value().length() >= MAX_PATH) { 761 to_path.value().length() >= MAX_PATH)
grt (UTC plus 2) 2014/01/26 23:55:08 i'd keep the braces here since the "if" statement'
M-A Ruel 2014/01/27 00:25:36 Done.
729 return false; 762 return false;
763
764 base::win::ScopedHandle from_handle(
765 ::CreateFile(from_path.value().c_str(),
766 GENERIC_READ,
767 kFileShareAll,
768 NULL,
769 OPEN_EXISTING,
770 FILE_FLAG_SEQUENTIAL_SCAN,
771 NULL));
772 if (!from_handle)
773 return false;
774 base::win::ScopedHandle to_handle(
775 ::CreateFile(to_path.value().c_str(),
776 GENERIC_WRITE,
777 0,
778 NULL,
779 CREATE_ALWAYS,
780 FILE_FLAG_SEQUENTIAL_SCAN,
781 NULL));
782 if (!to_handle)
783 return false;
784
785 const DWORD kBufferSize = 64 * 1024;
786 scoped_ptr<uint8_t[]> buffer(new uint8_t[kBufferSize]);
787 bool result = false;
788 while (true) {
789 DWORD size = 0;
790 if (!::ReadFile(from_handle, buffer.get(), kBufferSize, &size, NULL))
791 break;
792 if (size == 0) {
793 result = true;
794 break;
795 }
796 if (!::WriteFile(to_handle, buffer.get(), size, &size, NULL))
797 break;
730 } 798 }
731 799 // If failed, try to delete the destination file.
732 // Unlike the posix implementation that copies the file manually and discards 800 if (!result) {
733 // the ACL bits, CopyFile() copies the complete SECURITY_DESCRIPTOR and access 801 to_handle.Close();
734 // bits, which is usually not what we want. We can't do much about the 802 DeleteFile(to_path, false);
735 // SECURITY_DESCRIPTOR but at least remove the read only bit.
736 const wchar_t* dest = to_path.value().c_str();
737 if (!::CopyFile(from_path.value().c_str(), dest, false)) {
738 // Copy failed.
739 return false;
740 } 803 }
741 DWORD attrs = GetFileAttributes(dest); 804 return result;
742 if (attrs == INVALID_FILE_ATTRIBUTES) {
743 return false;
744 }
745 if (attrs & FILE_ATTRIBUTE_READONLY) {
746 SetFileAttributes(dest, attrs & ~FILE_ATTRIBUTE_READONLY);
747 }
748 return true;
749 } 805 }
750 806
751 bool CopyAndDeleteDirectory(const FilePath& from_path, 807 bool CopyAndDeleteDirectory(const FilePath& from_path,
752 const FilePath& to_path) { 808 const FilePath& to_path) {
753 ThreadRestrictions::AssertIOAllowed(); 809 ThreadRestrictions::AssertIOAllowed();
754 if (CopyDirectory(from_path, to_path, true)) { 810 if (CopyDirectory(from_path, to_path, true)) {
755 if (DeleteFile(from_path, true)) 811 if (DeleteFile(from_path, true))
756 return true; 812 return true;
757 813
758 // Like Move, this function is not transactional, so we just 814 // Like Move, this function is not transactional, so we just
759 // leave the copied bits behind if deleting from_path fails. 815 // leave the copied bits behind if deleting from_path fails.
760 // If to_path exists previously then we have already overwritten 816 // If to_path exists previously then we have already overwritten
761 // it by now, we don't get better off by deleting the new bits. 817 // it by now, we don't get better off by deleting the new bits.
762 } 818 }
763 return false; 819 return false;
764 } 820 }
765 821
766 } // namespace internal 822 } // namespace internal
767 } // namespace base 823 } // namespace base
OLDNEW
« no previous file with comments | « base/file_util_unittest.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698