Browse Source

Fix FAT delete of items with long name (#21528)

vyacheslav-shubin 4 years ago
parent
commit
8da8bf7e87
No account linked to committer's email address
1 changed files with 59 additions and 28 deletions
  1. 59
    28
      Marlin/src/sd/SdBaseFile.cpp

+ 59
- 28
Marlin/src/sd/SdBaseFile.cpp View File

@@ -1050,6 +1050,20 @@ int16_t SdBaseFile::read(void *buf, uint16_t nbyte) {
1050 1050
 }
1051 1051
 
1052 1052
 /**
1053
+ * Calculate a checksum for an 8.3 filename
1054
+ *
1055
+ * \param name The 8.3 file name to calculate
1056
+ *
1057
+ * \return The checksum byte
1058
+ */
1059
+uint8_t lfn_checksum(const uint8_t *name) {
1060
+  uint8_t sum = 0;
1061
+  for (uint8_t i = 11; i; i--)
1062
+    sum = ((sum & 1) << 7) + (sum >> 1) + *name++;
1063
+  return sum;
1064
+}
1065
+
1066
+/**
1053 1067
  * Read the next entry in a directory.
1054 1068
  *
1055 1069
  * \param[out] dir The dir_t struct that will receive the data.
@@ -1065,51 +1079,68 @@ int8_t SdBaseFile::readDir(dir_t *dir, char *longFilename) {
1065 1079
   // if not a directory file or miss-positioned return an error
1066 1080
   if (!isDir() || (0x1F & curPosition_)) return -1;
1067 1081
 
1082
+  #define INVALIDATE_LONGNAME() (longFilename[0] = longFilename[1] = '\0')
1083
+
1068 1084
   // If we have a longFilename buffer, mark it as invalid.
1069 1085
   // If a long filename is found it will be filled automatically.
1070
-  if (longFilename) { longFilename[0] = '\0'; longFilename[1] = '\0'; }
1086
+  if (longFilename) INVALIDATE_LONGNAME();
1087
+
1088
+  uint8_t checksum_error = 0xFF, checksum = 0;
1071 1089
 
1072 1090
   while (1) {
1073 1091
 
1074 1092
     n = read(dir, sizeof(dir_t));
1075 1093
     if (n != sizeof(dir_t)) return n ? -1 : 0;
1076 1094
 
1077
-    // last entry if DIR_NAME_FREE
1095
+    // Last entry if DIR_NAME_FREE
1078 1096
     if (dir->name[0] == DIR_NAME_FREE) return 0;
1079 1097
 
1080
-    // skip deleted entry and entry for .  and ..
1098
+    // Skip deleted entry and entry for . and ..
1081 1099
     if (dir->name[0] == DIR_NAME_DELETED || dir->name[0] == '.') {
1082
-      if (longFilename) { longFilename[0] = '\0'; longFilename[1] = '\0'; } // Invalidate erased file long name, if any
1100
+      if (longFilename) INVALIDATE_LONGNAME();   // Invalidate erased file long name, if any
1083 1101
       continue;
1084 1102
     }
1085 1103
 
1086
-    // Fill the long filename if we have a long filename entry.
1087
-    // Long filename entries are stored before the short filename.
1088
-    if (longFilename && DIR_IS_LONG_NAME(dir)) {
1089
-      vfat_t *VFAT = (vfat_t*)dir;
1090
-      // Sanity-check the VFAT entry. The first cluster is always set to zero. And the sequence number should be higher than 0
1091
-      if (VFAT->firstClusterLow == 0) {
1092
-        const uint8_t seq = VFAT->sequenceNumber & 0x1F;
1093
-        if (WITHIN(seq, 1, MAX_VFAT_ENTRIES)) {
1094
-          // TODO: Store the filename checksum to verify if a long-filename-unaware system modified the file table.
1095
-          n = (seq - 1) * (FILENAME_LENGTH);
1096
-          LOOP_L_N(i, FILENAME_LENGTH) {
1097
-            uint16_t utf16_ch = (i < 5) ? VFAT->name1[i] : (i < 11) ? VFAT->name2[i - 5] : VFAT->name3[i - 11];
1098
-            #if ENABLED(UTF_FILENAME_SUPPORT)
1099
-              // We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks
1100
-              // needs static bytes addressing. So here just store full UTF-16LE words to re-convert later.
1101
-              uint16_t idx = (n + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding
1102
-              longFilename[idx]     =  utf16_ch       & 0xFF;
1103
-              longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF;
1104
-            #else
1105
-              // Replace all multibyte characters to '_'
1106
-              longFilename[n + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF);
1107
-            #endif
1104
+    if (longFilename) {
1105
+      // Fill the long filename if we have a long filename entry.
1106
+      // Long filename entries are stored before the short filename.
1107
+      if (DIR_IS_LONG_NAME(dir)) {
1108
+        vfat_t *VFAT = (vfat_t*)dir;
1109
+        // Sanity-check the VFAT entry. The first cluster is always set to zero. And the sequence number should be higher than 0
1110
+        if (VFAT->firstClusterLow == 0) {
1111
+          const uint8_t seq = VFAT->sequenceNumber & 0x1F;
1112
+          if (WITHIN(seq, 1, MAX_VFAT_ENTRIES)) {
1113
+            n = (seq - 1) * (FILENAME_LENGTH);
1114
+            if (n == 0) {
1115
+              checksum = VFAT->checksum;
1116
+              checksum_error = 0;
1117
+            }
1118
+            else if (checksum != VFAT->checksum) // orphan detected
1119
+              checksum_error = 1;
1120
+
1121
+            LOOP_L_N(i, FILENAME_LENGTH) {
1122
+              const uint16_t utf16_ch = (i >= 11) ? VFAT->name3[i - 11] : (i >= 5) ? VFAT->name2[i - 5] : VFAT->name1[i];
1123
+              #if ENABLED(UTF_FILENAME_SUPPORT)
1124
+                // We can't reconvert to UTF-8 here as UTF-8 is variable-size encoding, but joining LFN blocks
1125
+                // needs static bytes addressing. So here just store full UTF-16LE words to re-convert later.
1126
+                uint16_t idx = (n + i) * 2; // This is fixed as FAT LFN always contain UTF-16LE encoding
1127
+                longFilename[idx] = utf16_ch & 0xFF;
1128
+                longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF;
1129
+              #else
1130
+                // Replace all multibyte characters to '_'
1131
+                longFilename[n + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF);
1132
+              #endif
1133
+            }
1134
+            // If this VFAT entry is the last one, add a NUL terminator at the end of the string
1135
+            if (VFAT->sequenceNumber & 0x40)
1136
+                longFilename[(n + FILENAME_LENGTH) * LONG_FILENAME_CHARSIZE] = '\0';
1108 1137
           }
1109
-          // If this VFAT entry is the last one, add a NUL terminator at the end of the string
1110
-          if (VFAT->sequenceNumber & 0x40) longFilename[(n + FILENAME_LENGTH) * LONG_FILENAME_CHARSIZE] = '\0';
1111 1138
         }
1112 1139
       }
1140
+      else {
1141
+        if (!checksum_error && lfn_checksum(dir->name) != checksum) checksum_error = 1; // orphan detected
1142
+        if (checksum_error) INVALIDATE_LONGNAME();
1143
+      }
1113 1144
     }
1114 1145
 
1115 1146
     // Post-process normal file or subdirectory longname, if any

Loading…
Cancel
Save