Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Tools: hv: Reopen the devices if read() or write() returns errors

The state machine in the hv_utils driver can run out of order in some
corner cases, e.g. if the kvp daemon doesn't call write() fast enough
due to some reason, kvp_timeout_func() can run first and move the state
to HVUTIL_READY; next, when kvp_on_msg() is called it returns -EINVAL
since kvp_transaction.state is smaller than HVUTIL_USERSPACE_REQ; later,
the daemon's write() gets an error -EINVAL, and the daemon will exit().

We can reproduce the issue by sending a SIGSTOP signal to the daemon, wait
for 1 minute, and send a SIGCONT signal to the daemon: the daemon will
exit() quickly.

We can fix the issue by forcing a reset of the device (which means the
daemon can close() and open() the device again) and doing extra necessary
clean-up.

Signed-off-by: Dexuan Cui <decui@microsoft.com>
Reviewed-by: Michael Kelley <mikelley@microsoft.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>

authored by

Dexuan Cui and committed by
Sasha Levin
9fc3c01a 3a6fb6c4

+91 -31
+32 -5
tools/hv/hv_fcopy_daemon.c
··· 80 80 81 81 error = 0; 82 82 done: 83 + if (error) 84 + target_fname[0] = '\0'; 83 85 return error; 84 86 } 85 87 ··· 110 108 return ret; 111 109 } 112 110 111 + /* 112 + * Reset target_fname to "" in the two below functions for hibernation: if 113 + * the fcopy operation is aborted by hibernation, the daemon should remove the 114 + * partially-copied file; to achieve this, the hv_utils driver always fakes a 115 + * CANCEL_FCOPY message upon suspend, and later when the VM resumes back, 116 + * the daemon calls hv_copy_cancel() to remove the file; if a file is copied 117 + * successfully before suspend, hv_copy_finished() must reset target_fname to 118 + * avoid that the file can be incorrectly removed upon resume, since the faked 119 + * CANCEL_FCOPY message is spurious in this case. 120 + */ 113 121 static int hv_copy_finished(void) 114 122 { 115 123 close(target_fd); 124 + target_fname[0] = '\0'; 116 125 return 0; 117 126 } 118 127 static int hv_copy_cancel(void) 119 128 { 120 129 close(target_fd); 121 - unlink(target_fname); 130 + if (strlen(target_fname) > 0) { 131 + unlink(target_fname); 132 + target_fname[0] = '\0'; 133 + } 122 134 return 0; 123 135 124 136 } ··· 147 131 148 132 int main(int argc, char *argv[]) 149 133 { 150 - int fcopy_fd; 134 + int fcopy_fd = -1; 151 135 int error; 152 136 int daemonize = 1, long_index = 0, opt; 153 137 int version = FCOPY_CURRENT_VERSION; ··· 157 141 struct hv_do_fcopy copy; 158 142 __u32 kernel_modver; 159 143 } buffer = { }; 160 - int in_handshake = 1; 144 + int in_handshake; 161 145 162 146 static struct option long_options[] = { 163 147 {"help", no_argument, 0, 'h' }, ··· 186 170 openlog("HV_FCOPY", 0, LOG_USER); 187 171 syslog(LOG_INFO, "starting; pid is:%d", getpid()); 188 172 173 + reopen_fcopy_fd: 174 + if (fcopy_fd != -1) 175 + close(fcopy_fd); 176 + /* Remove any possible partially-copied file on error */ 177 + hv_copy_cancel(); 178 + in_handshake = 1; 189 179 fcopy_fd = open("/dev/vmbus/hv_fcopy", O_RDWR); 190 180 191 181 if (fcopy_fd < 0) { ··· 218 196 len = pread(fcopy_fd, &buffer, sizeof(buffer), 0); 219 197 if (len < 0) { 220 198 syslog(LOG_ERR, "pread failed: %s", strerror(errno)); 221 - exit(EXIT_FAILURE); 199 + goto reopen_fcopy_fd; 222 200 } 223 201 224 202 if (in_handshake) { ··· 253 231 254 232 } 255 233 234 + /* 235 + * pwrite() may return an error due to the faked CANCEL_FCOPY 236 + * message upon hibernation. Ignore the error by resetting the 237 + * dev file, i.e. closing and re-opening it. 238 + */ 256 239 if (pwrite(fcopy_fd, &error, sizeof(int), 0) != sizeof(int)) { 257 240 syslog(LOG_ERR, "pwrite failed: %s", strerror(errno)); 258 - exit(EXIT_FAILURE); 241 + goto reopen_fcopy_fd; 259 242 } 260 243 } 261 244 }
+21 -15
tools/hv/hv_kvp_daemon.c
··· 76 76 DNS 77 77 }; 78 78 79 - static int in_hand_shake = 1; 79 + static int in_hand_shake; 80 80 81 81 static char *os_name = ""; 82 82 static char *os_major = ""; ··· 1360 1360 1361 1361 int main(int argc, char *argv[]) 1362 1362 { 1363 - int kvp_fd, len; 1363 + int kvp_fd = -1, len; 1364 1364 int error; 1365 1365 struct pollfd pfd; 1366 1366 char *p; ··· 1400 1400 openlog("KVP", 0, LOG_USER); 1401 1401 syslog(LOG_INFO, "KVP starting; pid is:%d", getpid()); 1402 1402 1403 - kvp_fd = open("/dev/vmbus/hv_kvp", O_RDWR | O_CLOEXEC); 1404 - 1405 - if (kvp_fd < 0) { 1406 - syslog(LOG_ERR, "open /dev/vmbus/hv_kvp failed; error: %d %s", 1407 - errno, strerror(errno)); 1408 - exit(EXIT_FAILURE); 1409 - } 1410 - 1411 1403 /* 1412 1404 * Retrieve OS release information. 1413 1405 */ ··· 1412 1420 1413 1421 if (kvp_file_init()) { 1414 1422 syslog(LOG_ERR, "Failed to initialize the pools"); 1423 + exit(EXIT_FAILURE); 1424 + } 1425 + 1426 + reopen_kvp_fd: 1427 + if (kvp_fd != -1) 1428 + close(kvp_fd); 1429 + in_hand_shake = 1; 1430 + kvp_fd = open("/dev/vmbus/hv_kvp", O_RDWR | O_CLOEXEC); 1431 + 1432 + if (kvp_fd < 0) { 1433 + syslog(LOG_ERR, "open /dev/vmbus/hv_kvp failed; error: %d %s", 1434 + errno, strerror(errno)); 1415 1435 exit(EXIT_FAILURE); 1416 1436 } 1417 1437 ··· 1460 1456 if (len != sizeof(struct hv_kvp_msg)) { 1461 1457 syslog(LOG_ERR, "read failed; error:%d %s", 1462 1458 errno, strerror(errno)); 1463 - 1464 - close(kvp_fd); 1465 - return EXIT_FAILURE; 1459 + goto reopen_kvp_fd; 1466 1460 } 1467 1461 1468 1462 /* ··· 1619 1617 break; 1620 1618 } 1621 1619 1622 - /* Send the value back to the kernel. */ 1620 + /* 1621 + * Send the value back to the kernel. Note: the write() may 1622 + * return an error due to hibernation; we can ignore the error 1623 + * by resetting the dev file, i.e. closing and re-opening it. 1624 + */ 1623 1625 kvp_done: 1624 1626 len = write(kvp_fd, hv_msg, sizeof(struct hv_kvp_msg)); 1625 1627 if (len != sizeof(struct hv_kvp_msg)) { 1626 1628 syslog(LOG_ERR, "write failed; error: %d %s", errno, 1627 1629 strerror(errno)); 1628 - exit(EXIT_FAILURE); 1630 + goto reopen_kvp_fd; 1629 1631 } 1630 1632 } 1631 1633
+38 -11
tools/hv/hv_vss_daemon.c
··· 28 28 #include <stdbool.h> 29 29 #include <dirent.h> 30 30 31 + static bool fs_frozen; 32 + 31 33 /* Don't use syslog() in the function since that can cause write to disk */ 32 34 static int vss_do_freeze(char *dir, unsigned int cmd) 33 35 { ··· 157 155 continue; 158 156 } 159 157 error |= vss_do_freeze(ent->mnt_dir, cmd); 160 - if (error && operation == VSS_OP_FREEZE) 161 - goto err; 158 + if (operation == VSS_OP_FREEZE) { 159 + if (error) 160 + goto err; 161 + fs_frozen = true; 162 + } 162 163 } 163 164 164 165 endmntent(mounts); 165 166 166 167 if (root_seen) { 167 168 error |= vss_do_freeze("/", cmd); 168 - if (error && operation == VSS_OP_FREEZE) 169 - goto err; 169 + if (operation == VSS_OP_FREEZE) { 170 + if (error) 171 + goto err; 172 + fs_frozen = true; 173 + } 170 174 } 175 + 176 + if (operation == VSS_OP_THAW && !error) 177 + fs_frozen = false; 171 178 172 179 goto out; 173 180 err: ··· 186 175 endmntent(mounts); 187 176 } 188 177 vss_operate(VSS_OP_THAW); 178 + fs_frozen = false; 189 179 /* Call syslog after we thaw all filesystems */ 190 180 if (ent) 191 181 syslog(LOG_ERR, "FREEZE of %s failed; error:%d %s", ··· 208 196 209 197 int main(int argc, char *argv[]) 210 198 { 211 - int vss_fd, len; 199 + int vss_fd = -1, len; 212 200 int error; 213 201 struct pollfd pfd; 214 202 int op; 215 203 struct hv_vss_msg vss_msg[1]; 216 204 int daemonize = 1, long_index = 0, opt; 217 - int in_handshake = 1; 205 + int in_handshake; 218 206 __u32 kernel_modver; 219 207 220 208 static struct option long_options[] = { ··· 244 232 openlog("Hyper-V VSS", 0, LOG_USER); 245 233 syslog(LOG_INFO, "VSS starting; pid is:%d", getpid()); 246 234 235 + reopen_vss_fd: 236 + if (vss_fd != -1) 237 + close(vss_fd); 238 + if (fs_frozen) { 239 + if (vss_operate(VSS_OP_THAW) || fs_frozen) { 240 + syslog(LOG_ERR, "failed to thaw file system: err=%d", 241 + errno); 242 + exit(EXIT_FAILURE); 243 + } 244 + } 245 + 246 + in_handshake = 1; 247 247 vss_fd = open("/dev/vmbus/hv_vss", O_RDWR); 248 248 if (vss_fd < 0) { 249 249 syslog(LOG_ERR, "open /dev/vmbus/hv_vss failed; error: %d %s", ··· 308 284 if (len != sizeof(struct hv_vss_msg)) { 309 285 syslog(LOG_ERR, "read failed; error:%d %s", 310 286 errno, strerror(errno)); 311 - close(vss_fd); 312 - return EXIT_FAILURE; 287 + goto reopen_vss_fd; 313 288 } 314 289 315 290 op = vss_msg->vss_hdr.operation; ··· 335 312 default: 336 313 syslog(LOG_ERR, "Illegal op:%d\n", op); 337 314 } 315 + 316 + /* 317 + * The write() may return an error due to the faked VSS_OP_THAW 318 + * message upon hibernation. Ignore the error by resetting the 319 + * dev file, i.e. closing and re-opening it. 320 + */ 338 321 vss_msg->error = error; 339 322 len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); 340 323 if (len != sizeof(struct hv_vss_msg)) { 341 324 syslog(LOG_ERR, "write failed; error: %d %s", errno, 342 325 strerror(errno)); 343 - 344 - if (op == VSS_OP_FREEZE) 345 - vss_operate(VSS_OP_THAW); 326 + goto reopen_vss_fd; 346 327 } 347 328 } 348 329