summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorestade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-09-16 00:29:22 +0000
committerestade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-09-16 00:29:22 +0000
commitfbea02332ae95f590c8a84019b582fa35e788a7c (patch)
treeeeff857a083663f3d5556fcff304b5fd05e32eb2
parent0018067c7afbdf516d1845b35a3a245d96910f66 (diff)
downloadchromium_src-fbea02332ae95f590c8a84019b582fa35e788a7c.zip
chromium_src-fbea02332ae95f590c8a84019b582fa35e788a7c.tar.gz
chromium_src-fbea02332ae95f590c8a84019b582fa35e788a7c.tar.bz2
Linux: print page to file rather than using shared memory to send it to the browser.
BUG=9847 adapted from patch by <minyu.huang [at] gmail> Review URL: http://codereview.chromium.org/203062 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@26308 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/file_util.h4
-rw-r--r--base/file_util_posix.cc25
-rw-r--r--chrome/browser/renderer_host/resource_message_filter.cc29
-rw-r--r--chrome/browser/renderer_host/resource_message_filter.h10
-rw-r--r--chrome/browser/renderer_host/resource_message_filter_gtk.cc63
-rw-r--r--chrome/common/render_messages_internal.h10
-rw-r--r--chrome/common/temp_scaffolding_stubs.cc48
-rw-r--r--chrome/common/temp_scaffolding_stubs.h4
-rw-r--r--chrome/renderer/print_web_view_helper_linux.cc96
-rw-r--r--printing/pdf_ps_metafile_linux.cc22
-rw-r--r--printing/pdf_ps_metafile_linux.h10
-rw-r--r--printing/pdf_ps_metafile_linux_unittest.cc17
12 files changed, 174 insertions, 164 deletions
diff --git a/base/file_util.h b/base/file_util.h
index ba8738c..6474f048 100644
--- a/base/file_util.h
+++ b/base/file_util.h
@@ -367,6 +367,10 @@ int ReadFile(const std::wstring& filename, char* data, int size);
int WriteFile(const FilePath& filename, const char* data, int size);
// Deprecated temporary compatibility function.
int WriteFile(const std::wstring& filename, const char* data, int size);
+#if defined(OS_POSIX)
+// Append the data to |fd|. Does not close |fd| when done.
+int WriteFileDescriptor(const int fd, const char* data, int size);
+#endif
// Gets the current working directory for the process.
bool GetCurrentDirectory(FilePath* path);
diff --git a/base/file_util_posix.cc b/base/file_util_posix.cc
index bd18a83..7c212c0 100644
--- a/base/file_util_posix.cc
+++ b/base/file_util_posix.cc
@@ -513,20 +513,23 @@ int WriteFile(const FilePath& filename, const char* data, int size) {
if (fd < 0)
return -1;
- // Allow for partial writes
+ int rv = WriteFileDescriptor(fd, data, size);
+ HANDLE_EINTR(close(fd));
+ return rv;
+}
+
+int WriteFileDescriptor(const int fd, const char* data, int size) {
+ // Allow for partial writes.
ssize_t bytes_written_total = 0;
- do {
- ssize_t bytes_written_partial =
- HANDLE_EINTR(write(fd, data + bytes_written_total,
- size - bytes_written_total));
- if (bytes_written_partial < 0) {
- HANDLE_EINTR(close(fd));
+ for (ssize_t bytes_written_partial = 0; bytes_written_total < size;
+ bytes_written_total += bytes_written_partial) {
+ bytes_written_partial =
+ HANDLE_EINTR(write(fd, data + bytes_written_total,
+ size - bytes_written_total));
+ if (bytes_written_partial < 0)
return -1;
- }
- bytes_written_total += bytes_written_partial;
- } while (bytes_written_total < size);
+ }
- HANDLE_EINTR(close(fd));
return bytes_written_total;
}
diff --git a/chrome/browser/renderer_host/resource_message_filter.cc b/chrome/browser/renderer_host/resource_message_filter.cc
index 095131f..4a5de3a 100644
--- a/chrome/browser/renderer_host/resource_message_filter.cc
+++ b/chrome/browser/renderer_host/resource_message_filter.cc
@@ -339,8 +339,10 @@ bool ResourceMessageFilter::OnMessageReceived(const IPC::Message& msg) {
IPC_MESSAGE_HANDLER(ViewHostMsg_DuplicateSection, OnDuplicateSection)
#endif
#if defined(OS_LINUX)
- IPC_MESSAGE_HANDLER(ViewHostMsg_AllocateShareMemory,
- OnAllocateShareMemory)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_AllocateTempFileForPrinting,
+ OnAllocateTempFileForPrinting)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_TempFileForPrintingWritten,
+ OnTempFileForPrintingWritten)
#endif
IPC_MESSAGE_HANDLER(ViewHostMsg_ResourceTypeStats, OnResourceTypeStats)
IPC_MESSAGE_HANDLER_DELAY_REPLY(ViewHostMsg_ResolveProxy, OnResolveProxy)
@@ -706,29 +708,6 @@ void ResourceMessageFilter::OnDuplicateSection(
}
#endif
-#if defined(OS_LINUX)
-void ResourceMessageFilter::OnAllocateShareMemory(
- size_t buffer_size,
- base::SharedMemoryHandle* browser_handle) {
- // We don't want to allocate a super big chunk of memory.
- // 32MB should be large enough for printing on Linux.
- if (buffer_size > 32 * 1024 * 1024) {
- *browser_handle = base::SharedMemory::NULLHandle();
- NOTREACHED() << "Buffer too large: " << buffer_size;
- return;
- }
- base::SharedMemory shared_buf;
- shared_buf.Create(L"", false, false, buffer_size);
- if (shared_buf.Map(buffer_size)) {
- shared_buf.GiveToProcess(base::GetCurrentProcessHandle(), browser_handle);
- } else {
- *browser_handle = base::SharedMemory::NULLHandle();
- NOTREACHED() << "Cannot map buffer";
- return;
- }
-}
-#endif
-
void ResourceMessageFilter::OnResourceTypeStats(
const WebCache::ResourceTypeStats& stats) {
HISTOGRAM_COUNTS("WebCoreCache.ImagesSizeKB",
diff --git a/chrome/browser/renderer_host/resource_message_filter.h b/chrome/browser/renderer_host/resource_message_filter.h
index 897e373..dceaa3d 100644
--- a/chrome/browser/renderer_host/resource_message_filter.h
+++ b/chrome/browser/renderer_host/resource_message_filter.h
@@ -52,6 +52,9 @@ struct WebScreenInfo;
}
struct ViewHostMsg_ScriptedPrint_Params;
+#if defined(OS_LINUX)
+struct ViewHostMsg_DidPrintPage_Params;
+#endif
// This class filters out incoming IPC messages for network requests and
// processes them on the IPC thread. As a result, network requests are not
@@ -204,10 +207,11 @@ class ResourceMessageFilter : public IPC::ChannelProxy::MessageFilter,
#endif
#if defined(OS_LINUX)
- // Used to ask the browser allocate a block of shared memory for the renderer
+ // Used to ask the browser allocate a temporary file for the renderer
// to fill in resulting PDF in renderer.
- void OnAllocateShareMemory(size_t buffer_size,
- base::SharedMemoryHandle* browser_handle);
+ void OnAllocateTempFileForPrinting(base::FileDescriptor* temp_file_fd,
+ int* fd_in_browser);
+ void OnTempFileForPrintingWritten(int fd_in_browser);
#endif
void OnResourceTypeStats(const WebKit::WebCache::ResourceTypeStats& stats);
diff --git a/chrome/browser/renderer_host/resource_message_filter_gtk.cc b/chrome/browser/renderer_host/resource_message_filter_gtk.cc
index 1dd964d..8533cac 100644
--- a/chrome/browser/renderer_host/resource_message_filter_gtk.cc
+++ b/chrome/browser/renderer_host/resource_message_filter_gtk.cc
@@ -4,11 +4,20 @@
#include "chrome/browser/renderer_host/resource_message_filter.h"
+#include <fcntl.h>
+#include <map>
+
+#include "app/l10n_util.h"
#include "base/clipboard.h"
+#include "base/file_util.h"
#include "base/gfx/gtk_native_view_id_manager.h"
+#include "base/path_service.h"
+#include "base/singleton.h"
#include "chrome/browser/chrome_thread.h"
+#include "chrome/common/chrome_paths.h"
#include "chrome/common/render_messages.h"
#include "chrome/common/x11_util.h"
+#include "grit/generated_resources.h"
#include "webkit/api/public/WebScreenInfo.h"
#include "webkit/api/public/x11/WebScreenInfoFactory.h"
@@ -16,6 +25,16 @@
using WebKit::WebScreenInfo;
using WebKit::WebScreenInfoFactory;
+namespace {
+
+typedef std::map<int, FilePath> FdMap;
+
+struct PrintingFileDescriptorMap {
+ FdMap map;
+};
+
+} // namespace
+
// We get null window_ids passed into the two functions below; please see
// http://crbug.com/9060 for more details.
@@ -210,3 +229,47 @@ void ResourceMessageFilter::OnClipboardReadHTML(Clipboard::Buffer buffer,
this, &ResourceMessageFilter::DoOnClipboardReadHTML, buffer,
reply_msg));
}
+
+// Called on the IO thread.
+void ResourceMessageFilter::OnAllocateTempFileForPrinting(
+ base::FileDescriptor* temp_file_fd, int* fd_in_browser) {
+ temp_file_fd->fd = *fd_in_browser = -1;
+
+ FilePath path;
+ if (!file_util::CreateTemporaryFile(&path))
+ return;
+
+ int fd = open(path.value().c_str(), O_WRONLY);
+ if (fd < 0)
+ return;
+
+ // We need to remember the FilePath of the temporary file because we need
+ // it when we want to rename/move it, and more importantly, to print it
+ // when we print by using gtk_print_job_set_source_file().
+ FdMap* map = &Singleton<PrintingFileDescriptorMap>::get()->map;
+ FdMap::iterator it = map->find(fd);
+ if (it != map->end()) {
+ NOTREACHED() << "The file descriptor is in use. fd=" << fd;
+ return;
+ }
+
+ (*map)[fd] = path;
+ temp_file_fd->fd = *fd_in_browser = fd;
+ temp_file_fd->auto_close = true;
+}
+
+// Called on the IO thread.
+void ResourceMessageFilter::OnTempFileForPrintingWritten(int fd_in_browser) {
+ FdMap* map = &Singleton<PrintingFileDescriptorMap>::get()->map;
+ FdMap::iterator it = map->find(fd_in_browser);
+ if (it == map->end()) {
+ NOTREACHED() << "Got a file descriptor that we didn't pass to the "
+ "renderer: " << fd_in_browser;
+ return;
+ }
+
+ // TODO(estade): print it.
+
+ // Erase the entry in the map.
+ map->erase(it);
+}
diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h
index 806c0e5..2916c17 100644
--- a/chrome/common/render_messages_internal.h
+++ b/chrome/common/render_messages_internal.h
@@ -1396,11 +1396,13 @@ IPC_BEGIN_MESSAGES(ViewHost)
#endif
#if defined(OS_LINUX)
- // Asks the browser create a block of shared memory for the renderer to fill
+ // Asks the browser create a temporary file for the renderer to fill
// in resulting NativeMetafile in printing.
- IPC_SYNC_MESSAGE_ROUTED1_1(ViewHostMsg_AllocateShareMemory,
- size_t /* buffer size */,
- base::SharedMemoryHandle /* browser handle */)
+ IPC_SYNC_MESSAGE_CONTROL0_2(ViewHostMsg_AllocateTempFileForPrinting,
+ base::FileDescriptor /* temp file fd */,
+ int /* fd in browser*/)
+ IPC_MESSAGE_CONTROL1(ViewHostMsg_TempFileForPrintingWritten,
+ int /* fd in browser */)
#endif
// Provide the browser process with information about the WebCore resource
diff --git a/chrome/common/temp_scaffolding_stubs.cc b/chrome/common/temp_scaffolding_stubs.cc
index 9b1ed6d..76d2d60 100644
--- a/chrome/common/temp_scaffolding_stubs.cc
+++ b/chrome/common/temp_scaffolding_stubs.cc
@@ -13,12 +13,8 @@
#include "chrome/browser/rlz/rlz.h"
#if defined(OS_LINUX)
-#include "base/path_service.h"
#include "chrome/browser/dock_info.h"
-#include "chrome/browser/tab_contents/tab_contents.h"
-#include "chrome/common/chrome_paths.h"
#include "chrome/common/render_messages.h"
-#include "printing/native_metafile.h"
#endif
#if defined(OS_MACOSX)
@@ -326,47 +322,3 @@ void BookmarkManager::Show(Profile* profile) {
#endif
-//------------------------------------------------------------------------------
-
-#if defined(OS_LINUX)
-// TODO(myhuang): This is a quick hack for testing purpose. We should implement
-// PrintViewManager and other related classes later on Linux.
-namespace printing {
-
-void PrintViewManager::DidPrintPage(
- const ViewHostMsg_DidPrintPage_Params& params) {
- base::SharedMemory shared_buf(params.metafile_data_handle, true);
- if (!shared_buf.Map(params.data_size)) {
- NOTREACHED() << "couldn't map";
- owner_.Stop();
- return;
- }
-
- // The only format we can use now is PDF since Cairo needs to create a
- // temporary file for a PostScript surface. We do not allow disk I/O in the
- // renderer.
- scoped_ptr<NativeMetafile> metafile(
- new NativeMetafile(printing::NativeMetafile::PDF));
-
- if (!metafile->Init(shared_buf.memory(), params.data_size)) {
- NOTREACHED() << "Invalid metafile header";
- shared_buf.Unmap();
- owner_.Stop();
- return;
- }
-
- // Save the PDF file to default download location.
- // NOTE: Existing file will be overwritten.
- FilePath default_save_path;
- if (!PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &default_save_path)) {
- NOTREACHED();
- }
- FilePath save_filename =
- default_save_path.Append(FilePath("chromium_printing_test.pdf"));
- metafile->SaveTo(save_filename);
- shared_buf.Unmap();
-}
-
-} // namespace printing
-
-#endif
diff --git a/chrome/common/temp_scaffolding_stubs.h b/chrome/common/temp_scaffolding_stubs.h
index 7b01f31..6f186ff 100644
--- a/chrome/common/temp_scaffolding_stubs.h
+++ b/chrome/common/temp_scaffolding_stubs.h
@@ -67,13 +67,9 @@ class PrintViewManager : public RenderViewHostDelegate::Printing {
NOTIMPLEMENTED();
}
-#if defined(OS_LINUX)
- virtual void DidPrintPage(const ViewHostMsg_DidPrintPage_Params& params);
-#else
virtual void DidPrintPage(const ViewHostMsg_DidPrintPage_Params& params) {
NOTIMPLEMENTED();
}
-#endif
private:
TabContents& owner_;
diff --git a/chrome/renderer/print_web_view_helper_linux.cc b/chrome/renderer/print_web_view_helper_linux.cc
index 211eb6f..b8e0ca2 100644
--- a/chrome/renderer/print_web_view_helper_linux.cc
+++ b/chrome/renderer/print_web_view_helper_linux.cc
@@ -4,6 +4,7 @@
#include "chrome/renderer/print_web_view_helper.h"
+#include "base/file_descriptor_posix.h"
#include "base/logging.h"
#include "chrome/common/render_messages.h"
#include "printing/native_metafile.h"
@@ -53,64 +54,49 @@ void PrintWebViewHelper::PrintPages(const ViewMsg_PrintPages_Params& params,
// TODO(myhuang): Send ViewHostMsg_DidGetPrintedPagesCount.
- if (page_count) {
- // We only can use PDF in the renderer because Cairo needs to create a
- // temporary file for a PostScript surface.
- printing::NativeMetafile metafile(printing::NativeMetafile::PDF);
- metafile.Init();
-
- ViewMsg_PrintPage_Params print_page_params;
- print_page_params.params = params.params;
- const gfx::Size& canvas_size = prep_frame_view.GetPrintCanvasSize();
- if (params.pages.empty()) {
- for (int i = 0; i < page_count; ++i) {
- print_page_params.page_number = i;
- PrintPage(print_page_params, canvas_size, frame, &metafile);
- }
- } else {
- for (size_t i = 0; i < params.pages.size(); ++i) {
- print_page_params.page_number = params.pages[i];
- PrintPage(print_page_params, canvas_size, frame, &metafile);
- }
- }
+ if (page_count == 0)
+ return;
- metafile.Close();
-
- // Get the size of the resulting metafile.
- unsigned int buf_size = metafile.GetDataSize();
- DCHECK_GT(buf_size, 0u);
-
- ViewHostMsg_DidPrintPage_Params did_page_params;
-
- // Ask the browser create the shared memory for us.
- if (Send(new ViewHostMsg_AllocateShareMemory(
- routing_id(),
- buf_size,
- &did_page_params.metafile_data_handle))) {
- if (did_page_params.metafile_data_handle.fd > -1) {
- base::SharedMemory shared_buf(did_page_params.metafile_data_handle,
- false);
- if (shared_buf.Map(buf_size)) {
- if (metafile.GetData(shared_buf.memory(), buf_size)) {
- // FIXME(myhuang): This is for testing purpose at this moment.
- // We use this message to pass the resulting PDF to the browser,
- // and the browser will save this PDF on the disk.
- did_page_params.data_size = buf_size;
- Send(new ViewHostMsg_DidPrintPage(routing_id(), did_page_params));
- } else {
- NOTREACHED() << "GetData() failed";
- }
- shared_buf.Unmap();
- } else {
- NOTREACHED() << "Buffer mapping failed";
- }
- } else {
- NOTREACHED() << "Buffer allocation failed";
- }
- } else {
- NOTREACHED() << "Buffer allocation failed";
+ // We only can use PDF in the renderer because Cairo needs to create a
+ // temporary file for a PostScript surface.
+ printing::NativeMetafile metafile(printing::NativeMetafile::PDF);
+ metafile.Init();
+
+ ViewMsg_PrintPage_Params print_page_params;
+ print_page_params.params = params.params;
+ const gfx::Size& canvas_size = prep_frame_view.GetPrintCanvasSize();
+ if (params.pages.empty()) {
+ for (int i = 0; i < page_count; ++i) {
+ print_page_params.page_number = i;
+ PrintPage(print_page_params, canvas_size, frame, &metafile);
+ }
+ } else {
+ for (size_t i = 0; i < params.pages.size(); ++i) {
+ print_page_params.page_number = params.pages[i];
+ PrintPage(print_page_params, canvas_size, frame, &metafile);
}
}
+
+ metafile.Close();
+
+ // Get the size of the resulting metafile.
+ unsigned int buf_size = metafile.GetDataSize();
+ DCHECK_GT(buf_size, 0u);
+
+ base::FileDescriptor fd;
+ int fd_in_browser = -1;
+
+ // Ask the browser to open a file for us.
+ if (!Send(new ViewHostMsg_AllocateTempFileForPrinting(&fd,
+ &fd_in_browser))) {
+ return;
+ }
+
+ if (!metafile.SaveTo(fd))
+ return;
+
+ // Tell the browser we've finished writing the file.
+ Send(new ViewHostMsg_TempFileForPrintingWritten(fd_in_browser));
}
void PrintWebViewHelper::PrintPage(const ViewMsg_PrintPage_Params& params,
diff --git a/printing/pdf_ps_metafile_linux.cc b/printing/pdf_ps_metafile_linux.cc
index d55795c..d5fe5fb 100644
--- a/printing/pdf_ps_metafile_linux.cc
+++ b/printing/pdf_ps_metafile_linux.cc
@@ -16,6 +16,8 @@
#include <map>
+#include "base/eintr_wrapper.h"
+#include "base/file_descriptor_posix.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/singleton.h"
@@ -471,7 +473,7 @@ bool PdfPsMetafile::GetData(void* dst_buffer, size_t dst_buffer_size) const {
return true;
}
-bool PdfPsMetafile::SaveTo(const FilePath& filename) const {
+bool PdfPsMetafile::SaveTo(const base::FileDescriptor& fd) const {
// We need to check at least these two members to ensure that either Init()
// has been called to initialize |all_pages_|, or metafile has been closed.
// Passing these two checks also implies that surface_, page_surface_, and
@@ -479,15 +481,21 @@ bool PdfPsMetafile::SaveTo(const FilePath& filename) const {
DCHECK(!context_);
DCHECK(!all_pages_.empty());
- const unsigned int data_size = GetDataSize();
- const unsigned int bytes_written =
- file_util::WriteFile(filename, all_pages_.data(), data_size);
- if (bytes_written != data_size) {
- DLOG(ERROR) << "Failed to save file: " << filename.value();
+ if (fd.fd < 0) {
+ DLOG(ERROR) << "Invalid file descriptor!";
return false;
}
- return true;
+ bool success = true;
+ if (file_util::WriteFileDescriptor(fd.fd, all_pages_.data(),
+ GetDataSize()) < 0) {
+ DLOG(ERROR) << "Failed to save file with fd " << fd.fd;
+ success = false;
+ }
+
+ if (fd.auto_close)
+ HANDLE_EINTR(close(fd.fd));
+ return success;
}
void PdfPsMetafile::CleanUpAll() {
diff --git a/printing/pdf_ps_metafile_linux.h b/printing/pdf_ps_metafile_linux.h
index 04b0ca1e..faf6ab7 100644
--- a/printing/pdf_ps_metafile_linux.h
+++ b/printing/pdf_ps_metafile_linux.h
@@ -12,6 +12,10 @@
typedef struct _cairo_surface cairo_surface_t;
typedef struct _cairo cairo_t;
+namespace base {
+class FileDescriptor;
+}
+
class FilePath;
namespace printing {
@@ -77,10 +81,10 @@ class PdfPsMetafile {
// Returns true only when success.
bool GetData(void* dst_buffer, size_t dst_buffer_size) const;
- // Saves PDF/PS contents stored in buffer |all_pages_| into |filename| on
- // the disk.
+ // Saves PDF/PS contents stored in buffer |all_pages_| into the file
+ // associated with |fd|.
// This function should ONLY be called after PDF/PS file is closed.
- bool SaveTo(const FilePath& filename) const;
+ bool SaveTo(const base::FileDescriptor& fd) const;
private:
// Cleans up all resources.
diff --git a/printing/pdf_ps_metafile_linux_unittest.cc b/printing/pdf_ps_metafile_linux_unittest.cc
index 7339559..e852b37 100644
--- a/printing/pdf_ps_metafile_linux_unittest.cc
+++ b/printing/pdf_ps_metafile_linux_unittest.cc
@@ -4,15 +4,24 @@
#include "printing/pdf_ps_metafile_linux.h"
+#include <fcntl.h>
#include <string>
#include <vector>
+#include "base/file_descriptor_posix.h"
#include "base/file_util.h"
#include "testing/gtest/include/gtest/gtest.h"
typedef struct _cairo cairo_t;
-TEST(PdfTest, ThreePages) {
+class PdfPsTest : public testing::Test {
+ protected:
+ base::FileDescriptor DevNullFD() {
+ return base::FileDescriptor(open("/dev/null", O_WRONLY), true);
+ }
+};
+
+TEST_F(PdfPsTest, Pdf) {
// Tests in-renderer constructor.
printing::PdfPsMetafile pdf(printing::PdfPsMetafile::PDF);
EXPECT_TRUE(pdf.Init());
@@ -53,10 +62,10 @@ TEST(PdfTest, ThreePages) {
EXPECT_EQ(header.find("%PDF", 0), 0u);
// Tests if we can save data.
- EXPECT_TRUE(pdf.SaveTo(FilePath("/dev/null")));
+ EXPECT_TRUE(pdf.SaveTo(DevNullFD()));
}
-TEST(PsTest, TwoPages) {
+TEST_F(PdfPsTest, Ps) {
// Tests in-renderer constructor.
printing::PdfPsMetafile ps(printing::PdfPsMetafile::PS);
EXPECT_TRUE(ps.Init());
@@ -97,5 +106,5 @@ TEST(PsTest, TwoPages) {
EXPECT_EQ(header.find("%!PS", 0), 0u);
// Tests if we can save data.
- EXPECT_TRUE(ps.SaveTo(FilePath("/dev/null")));
+ EXPECT_TRUE(ps.SaveTo(DevNullFD()));
}