{"id":104395,"date":"2020-10-23T07:00:00","date_gmt":"2020-10-23T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=104395"},"modified":"2020-10-23T09:01:22","modified_gmt":"2020-10-23T16:01:22","slug":"20201023-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20201023-00\/?p=104395","title":{"rendered":"How can I tell whether a file is on an SSD?"},"content":{"rendered":"<p>You might want your program to change its behavior depending on whether the file you are operating on is on an SSD or not. Maybe you&#8217;d use <code>Prefetch\u00adVirtual\u00adMemory<\/code> to get the contents of a memory-mapped file into memory more efficiently if the file is on a hard drive, but not bother if the file is on an SSD, since the SSD can produce the data quickly enough anyway.<\/p>\n<pre>bool IsFileOnSsd(PCWSTR filePath)\r\n{\r\n  wil::unique_hfile volume = GetVolumeHandleForFile(filePath);\r\n\r\n  STORAGE_PROPERTY_QUERY query{};\r\n  query.PropertyId = StorageDeviceSeekPenaltyProperty;\r\n  query.QueryType = PropertyStandardQuery;\r\n  DWORD bytesWritten;\r\n  DEVICE_SEEK_PENALTY_DESCRIPTOR result{};\r\n\r\n  if (DeviceIoControl(volume.get(), IOCTL_STORAGE_QUERY_PROPERTY,\r\n      &amp;query, sizeof(query),\r\n      &amp;result, sizeof(result),\r\n      &amp;bytesWritten, nullptr)) {\r\n    return !result.IncursSeekPenalty;\r\n  }\r\n  return false;\r\n}\r\n<\/pre>\n<p>This takes advantage of the trick we learned last time where <a title=\"Taking a shortcut: You can query properties from a volume, and it will forward to the physical drive\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20201022-00\/?p=104391\"> you can make a storage query against a volume<\/a>, and it will report the answer if the volume has a single extent.<\/p>\n<p>We aren&#8217;t so much checking whether it&#8217;s on an SSD drive as we are checking whether seeks are free. That is true for SSDs, but it&#8217;s also true for RAM drives. But RAM drives are even faster than SSDs, so I think it&#8217;s okay to treat them as &#8220;super-awesome SSDs&#8221;.<\/p>\n<p>The <a title=\"How do I get from a file path to the volume that holds it?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20201020-00\/?p=104385\"> <code>Get\u00adVolume\u00adHandle\u00adFor\u00adFile<\/code> function we wrote a few days ago<\/a> will throw if the file is remote (on a network). We probably want to report network files as &#8220;not on an SSD&#8221;, because even if they are on an SSD on the server, the network transmission cost will make it feel slow.<\/p>\n<pre>wil::unique_hfile GetVolumeHandleForFile(PCWSTR filePath)\r\n{\r\n  wchar_t volumePath[MAX_PATH];\r\n  THROW_IF_WIN32_BOOL_FALSE(GetVolumePathName(filePath,\r\n                                volumePath, ARRAYSIZE(volumePath)));\r\n\r\n  wchar_t volumeName[MAX_PATH];\r\n  <span style=\"color: blue;\">if (!GetVolumeNameForVolumeMountPoint(volumePath,\r\n                                volumeName, ARRAYSIZE(volumeName))) {\r\n    return {};\r\n  }<\/span>\r\n\r\n  auto length = wcslen(volumeName);\r\n  if (length &amp;&amp; volumeName[length - 1] == L'\\\\')\r\n  {\r\n    volumeName[length - 1] = L'\\0';\r\n  }\r\n\r\n  wil::unique_hfile result{ CreateFile(volumeName, 0,\r\n                FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\r\n                nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr) };\r\n  THROW_LAST_ERROR_IF(!result);\r\n  return result;\r\n}\r\n<\/pre>\n<p>We would then check whether a volume was gotten:<\/p>\n<pre>bool IsFileOnSsd(PCWSTR filePath)\r\n{\r\n  wil::unique_hfile volume = GetVolumeHandleForFile(filePath);\r\n  <span style=\"color: blue;\">if (!volume) return false;<\/span>\r\n\r\n  STORAGE_PROPERTY_QUERY query{};\r\n  query.PropertyId = StorageDeviceSeekPenaltyProperty;\r\n  query.QueryType = PropertyStandardQuery;\r\n  DWORD bytesWritten;\r\n  DEVICE_SEEK_PENALTY_DESCRIPTOR result{};\r\n\r\n  if (DeviceIoControl(volume.get(), IOCTL_STORAGE_QUERY_PROPERTY,\r\n      &amp;query, sizeof(query),\r\n      &amp;result, sizeof(result),\r\n      &amp;bytesWritten, nullptr)) {\r\n    return !result.IncursSeekPenalty;\r\n  }\r\n  return false;\r\n}\r\n<\/pre>\n<p>As we noted last time, the query against a volume will fail if the volume spans multiple physical disks. If you have a volume that spans multiple SSDs, this function will nevertheless report that it isn&#8217;t an SSD.<\/p>\n<p>So we probably would be better off checking the SSD-ness of every physical drive in the volume. That&#8217;s a lot of work, so I&#8217;m going to cheat and check just the first physical drive in the volume, on the theory that when people create multi-drive volumes, they&#8217;re going to be drives of similar performance characteristics.<\/p>\n<pre>bool IsFileOnSsd(PCWSTR filePath)\r\n{\r\n  wil::unique_hfile volume = GetVolumeHandleForFile(filePath);\r\n  if (!volume) return false;\r\n\r\n  <span style=\"color: blue;\">wil::unique_hfile disk =\r\n    GetFirstPhysicalDiskHandleForVolume(volume.get());\r\n  if (!disk) return false;<\/span>\r\n\r\n  STORAGE_PROPERTY_QUERY query{};\r\n  query.PropertyId = StorageDeviceSeekPenaltyProperty;\r\n  query.QueryType = PropertyStandardQuery;\r\n  DWORD bytesWritten;\r\n  DEVICE_SEEK_PENALTY_DESCRIPTOR result{};\r\n\r\n  if (DeviceIoControl(<span style=\"color: blue;\">disk.<\/span>get(), IOCTL_STORAGE_QUERY_PROPERTY,\r\n      &amp;query, sizeof(query),\r\n      &amp;result, sizeof(result),\r\n      &amp;bytesWritten, nullptr)) {\r\n    return !result.IncursSeekPenalty;\r\n  }\r\n  return false;\r\n}\r\n<\/pre>\n<p><b>Bonus chatter<\/b>: Many people cheat even further and also assume that the volume is mounted as a drive letter. In that case, obtaining the volume handle for the file is a simple matter of opening <code>\\\\.\\X:<\/code>, where <code>X:<\/code> is the drive letter of the file you are interested in.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Putting the pieces together.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-104395","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Putting the pieces together.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104395","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=104395"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/104395\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=104395"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=104395"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=104395"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}