{"id":105822,"date":"2021-10-22T07:00:00","date_gmt":"2021-10-22T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105822"},"modified":"2021-10-22T07:17:02","modified_gmt":"2021-10-22T14:17:02","slug":"20211022-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20211022-00\/?p=105822","title":{"rendered":"Renaming a file is a multi-step process, only one of which is changing the name of the file"},"content":{"rendered":"<p>A customer reported that the <code>Read\u00adDirectory\u00adChangesW<\/code> function was reporting changes too soon. No, it wasn&#8217;t generating changes from the future, <i>\u00e0 la<\/i> <i><a href=\"http:\/\/en.wikipedia.org\/wiki\/Minority_Report_(film)\">Minority Report<\/a><\/i>. Rather, it generated rename notifications before the rename was complete.<\/p>\n<p>The customer came to this conclusion because they observed their program behaving like this:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th style=\"border-bottom: solid 1px black; border-right: solid 1px black;\">Thread 1<\/th>\n<th style=\"border-bottom: solid 1px black;\">Thread 2<\/th>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>Read\u00adDirectory\u00adChangesW(<wbr \/>\n    FILE_<wbr \/>NOTIFY_<wbr \/>CHANGE_<wbr \/>FILE_<wbr \/>NAME)<\/code>.<\/td>\n<\/tr>\n<tr>\n<td>Call <code>Move\u00adFile\u00adEx<\/code> to rename a file.<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td><code>Read\u00adDirectory\u00adChangesW<\/code> reports a rename occurred.<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>Tries to read the renamed file and gets <code>ERROR_<wbr \/>SHARING_<wbr \/>VIOLATION<\/code>.<\/td>\n<\/tr>\n<tr>\n<td><code>Move\u00adFile\u00adEx<\/code> returns.<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>Tries to read the renamed file and succeeds.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The <code>Read\u00adDirectory\u00adChangesW<\/code> function reports the rename before the <code>Move\u00adFile\u00adEx<\/code> function returns, and consequently before the rename has completed.<\/p>\n<p>What&#8217;s going on here?<\/p>\n<p>Well, the first thing to observe is that the customer&#8217;s conclusion doesn&#8217;t match the evidence. Observe that the attempt to open the renamed file failed with <code>ERROR_<wbr \/>SHARING_<wbr \/>VIOLATION<\/code>, whereas they expected error would be <code>ERROR_<wbr \/>FILE_<wbr \/>NOT_<wbr \/>FOUND<\/code> if the file hadn&#8217;t been renamed yet. The fact that they&#8217;re getting <code>ERROR_<wbr \/>SHARING_<wbr \/>VIOLATION<\/code> means that the rename <i>really did occur<\/i>, but they are unable to access the renamed file due to a sharing violation.<\/p>\n<p>Okay, let&#8217;s look at how renaming a file is performed internally. It&#8217;s a multi-step operation.<\/p>\n<ol>\n<li>Open the file with <code>DELETE<\/code> permission.<\/li>\n<li>Call <a href=\"https:\/\/docs.microsoft.com\/en-us\/windows-hardware\/drivers\/ddi\/ntifs\/nf-ntifs-ntsetinformationfile\"> <code>Nt\u00adSet\u00adInformation\u00adFile<\/code><\/a> with <code>File\u00adRename\u00adInformation<\/code>.<\/li>\n<li>Close the handle.<\/li>\n<\/ol>\n<p>Opening with <code>DELETE<\/code> permission grants permission to rename the file. The required permission is <code>DELETE<\/code> because the old name is being deleted.<\/p>\n<p>The call with <code>File\u00adRename\u00adInformation<\/code> is what actually renames the file, and it is here that the <code>Read\u00adDirectory\u00adChangesW<\/code> is signaled.<\/p>\n<p>Now that the rename is complete, the handle can be closed.<\/p>\n<p>It is technically correct for the <code>Read\u00adDirectory\u00adChangesW<\/code> to be signaled once the <code>Nt\u00adSet\u00adInformation\u00adFile<\/code> is done, because the file is well and truly renamed.<\/p>\n<p>Let&#8217;s look at that sharing violation again. The customer explained that they tried to open the file by doing this:<\/p>\n<pre>std::ifstream file(path, std::ios::binary, _SH_DENYNO);\r\n<\/pre>\n<p>The <code>_SH_<wbr \/>DENYNO<\/code> indicates that no sharing operations are denied. So why is sharing denied?<\/p>\n<p>You were faked out by a flag name that makes sense in context, but has ended up being confusing due to the passage of time.<\/p>\n<p>Let&#8217;s <a href=\"https:\/\/docs.microsoft.com\/en-us\/cpp\/c-runtime-library\/reference\/fsopen-wfsopen?view=msvc-160\"> look at those sharing flags<\/a> in context:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Flag<\/th>\n<th>Meaning<\/th>\n<th>Mnemonic<\/th>\n<\/tr>\n<tr>\n<td><code>_SH_DENYRD<\/code><\/td>\n<td>Deny read, allow write.<\/td>\n<td>Deny read.<\/td>\n<\/tr>\n<tr>\n<td><code>_SH_DENYWR<\/code><\/td>\n<td>Allow read, deny write.<\/td>\n<td>Deny write.<\/td>\n<\/tr>\n<tr>\n<td><code>_SH_DENYRW<\/code><\/td>\n<td>Deny read, deny write.<\/td>\n<td>Deny read and write.<\/td>\n<\/tr>\n<tr>\n<td><code>_SH_DENYNO<\/code><\/td>\n<td>Allow read, allow write.<\/td>\n<td>Deny none.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The mnemonic for <code>_SH_<wbr \/>DENY\u00adNO<\/code> is &#8220;Deny none&#8221;, but the word &#8220;none&#8221; is only with the context of read and write. You could say that it denies <a href=\"https:\/\/www.youtube.com\/watch?v=cSZfUnCK5qk\"> neither country <i>nor<\/i> western<\/a>.<\/p>\n<p>The important sharing mode here is neither read nor write. It&#8217;s <code>FILE_<wbr \/>SHARE_<wbr \/>DELETE<\/code>, which means &#8220;I&#8217;m okay with letting someone delete or rename the file while I have it open.&#8221;\u00b9 This is a sharing flag that programs really should be using more often than they do, and the fact that the C runtime doesn&#8217;t give you an easy way to set this sharing flag may be a contributing factor.<\/p>\n<p>If you call the <code>Create\u00adFile<\/code> function directly, then you can pass the <code>FILE_<wbr \/>SHARE_<wbr \/>DELETE<\/code> sharing flag, and then you&#8217;ll be able to open the file even before <code>Move\u00adFile\u00adEx<\/code> cleans up its handle.<\/p>\n<p>&#8220;So why not have <code>Read\u00adDirectory\u00adChangesW<\/code> wait until the handle is closed before raising the rename notification?&#8221;<\/p>\n<p>Well, for one thing, the file really has been renamed as soon as the <code>Nt\u00adSet\u00adInformation\u00adFile<\/code> is complete, so delaying the notification would be a little disingenuous. But seeing as it&#8217;s just a small delay, maybe that&#8217;s okay, seeing as the whole thing is a notification anyway, and notifications can be delayed for other reasons.<\/p>\n<p>But the real reason is that delaying the notification until the close of the handle could delay it indefinitely. The caller is not required to close the handle immediately after the <code>Nt\u00adSet\u00adInformation\u00adFile<\/code> returns. It could leave the handle open so it can perform other operations on the file. For example, maybe it&#8217;s a log file that is being renamed while it is still being actively written to. That log file&#8217;s new name takes effect immediately, but the handle won&#8217;t be closed for a long time yet.<\/p>\n<p>The customer confirmed that switching to a direct <code>Create\u00adFile<\/code> with <code>FILE_<wbr \/>SHARE_<wbr \/>DELETE<\/code> allowed them to open and read the file immediately after it was renamed.<\/p>\n<p>Moral of the story: Don&#8217;t forget <code>FILE_<wbr \/>SHARE_<wbr \/>DELETE<\/code>. It lets you coexist with code that is deleting or renaming the file you are looking at.<\/p>\n<p>\u00b9 My colleague Malcolm Smith, whom I rely on for all things filesystem, notes that the name <code>FILE_<wbr \/>SHARE_<wbr \/>DELETE<\/code> is rather misleading. Because the fact that you opened the file for <code>FILE_<wbr \/>SHARE_<wbr \/>DELETE<\/code> prevents it from being deleted, even though you&#8217;re allowing it! In Windows, when you mark a file for deletion, the deletion doesn&#8217;t take effect until all outstanding handles are closed, and holding a file open for <code>FILE_<wbr \/>SHARE_<wbr \/>DELETE<\/code> means that the last handle isn&#8217;t closed yet. What <code>FILE_<wbr \/>SHARE_<wbr \/>DELETE<\/code> does is allow the file to be opened by others in <code>DELETE<\/code> mode, which as it happens is a prerequisite for both deleting and renaming files.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you&#8217;re going to swoop in, make sure to swoop in carefully.<\/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-105822","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>If you&#8217;re going to swoop in, make sure to swoop in carefully.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105822","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=105822"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105822\/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=105822"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105822"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105822"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}