Browse Source

Merge pull request #4136 from flysand7/process-info-fix

[os2/process]: Don't free process info fields in partial success scenarios
Laytan 11 months ago
parent
commit
c1684d6335
2 changed files with 146 additions and 102 deletions
  1. 13 13
      core/os/os2/process.odin
  2. 133 89
      core/os/os2/process_windows.odin

+ 13 - 13
core/os/os2/process.odin

@@ -166,15 +166,15 @@ Process_Info :: struct {
 
 	This procedure obtains an information, specified by `selection` parameter of
 	a process given by `pid`.
-	
-	Use `free_process_info` to free the memory allocated by this procedure. In
-	case the function returns an error it may only have been an error for one part
-	of the information and you would still need to call it to free the other parts.
+
+	Use `free_process_info` to free the memory allocated by this procedure. The
+	`free_process_info` procedure needs to be called, even if this procedure
+	returned an error, as some of the fields may have been allocated.
 
 	**Note**: The resulting information may or may contain the fields specified
 	by the `selection` parameter. Always check whether the returned
 	`Process_Info` struct has the required fields before checking the error code
-	returned by this function.
+	returned by this procedure.
 */
 @(require_results)
 process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
@@ -188,14 +188,14 @@ process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator:
 	about a process that has been opened by the application, specified in
 	the `process` parameter.
 
-	Use `free_process_info` to free the memory allocated by this procedure. In
-	case the function returns an error it may only have been an error for one part
-	of the information and you would still need to call it to free the other parts.
+	Use `free_process_info` to free the memory allocated by this procedure. The
+	`free_process_info` procedure needs to be called, even if this procedure
+	returned an error, as some of the fields may have been allocated.
 
 	**Note**: The resulting information may or may contain the fields specified
 	by the `selection` parameter. Always check whether the returned
 	`Process_Info` struct has the required fields before checking the error code
-	returned by this function.
+	returned by this procedure.
 */
 @(require_results)
 process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {
@@ -208,14 +208,14 @@ process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields,
 	This procedure obtains the information, specified by `selection` parameter
 	about the currently running process.
 
-	Use `free_process_info` to free the memory allocated by this procedure. In
-	case the function returns an error it may only have been an error for one part
-	of the information and you would still need to call it to free the other parts.
+	Use `free_process_info` to free the memory allocated by this procedure. The
+	`free_process_info` procedure needs to be called, even if this procedure
+	returned an error, as some of the fields may have been allocated.
 
 	**Note**: The resulting information may or may contain the fields specified
 	by the `selection` parameter. Always check whether the returned
 	`Process_Info` struct has the required fields before checking the error code
-	returned by this function.
+	returned by this procedure.
 */
 @(require_results)
 current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (Process_Info, Error) {

+ 133 - 89
core/os/os2/process_windows.odin

@@ -93,16 +93,33 @@ read_memory_as_slice :: proc(h: win32.HANDLE, addr: rawptr, dest: []$T) -> (byte
 @(private="package")
 _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
 	info.pid = pid
-	defer if err != nil {
-		free_process_info(info, allocator)
+	// Note(flysand): Open the process handle right away to prevent some race
+	// conditions. Once the handle is open, the process will be kept alive by
+	// the OS.
+	ph := win32.INVALID_HANDLE_VALUE
+	if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} {
+		ph = win32.OpenProcess(
+			win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ,
+			false,
+			u32(pid),
+		)
+		if ph == win32.INVALID_HANDLE_VALUE {
+			err = _get_platform_error()
+			return
+		}
 	}
-
-	// Data obtained from process snapshots
-	if selection >= {.PPid, .Priority} {
+	defer if ph != win32.INVALID_HANDLE_VALUE {
+		win32.CloseHandle(ph)
+	}
+	snapshot_process: if selection >= {.PPid, .Priority} {
 		entry, entry_err := _process_entry_by_pid(info.pid)
 		if entry_err != nil {
-			err = General_Error.Not_Exist
-			return
+			err = entry_err
+			if entry_err == General_Error.Not_Exist {
+				return
+			} else {
+				break snapshot_process
+			}
 		}
 		if .PPid in selection {
 			info.fields += {.PPid}
@@ -113,29 +130,18 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			info.priority = int(entry.pcPriClassBase)
 		}
 	}
-	if .Executable_Path in selection { // snap module
-		info.executable_path = _process_exe_by_pid(pid, allocator) or_return
-		info.fields += {.Executable_Path}
-	}
-
-	ph := win32.INVALID_HANDLE_VALUE
-
-	if selection >= {.Command_Line, .Environment, .Working_Dir, .Username} { // need process handle
-		ph = win32.OpenProcess(
-			win32.PROCESS_QUERY_LIMITED_INFORMATION | win32.PROCESS_VM_READ,
-			false,
-			u32(pid),
-		)
-		if ph == win32.INVALID_HANDLE_VALUE {
-			err = _get_platform_error()
+	snapshot_modules: if .Executable_Path in selection {
+		exe_path: string
+		exe_path, err = _process_exe_by_pid(pid, allocator)
+		if _, ok := err.(runtime.Allocator_Error); ok {
 			return
+		} else if err != nil {
+			break snapshot_modules
 		}
+		info.executable_path = exe_path
+		info.fields += {.Executable_Path}
 	}
-	defer if ph != win32.INVALID_HANDLE_VALUE {
-		win32.CloseHandle(ph)
-	}
-
-	if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
+	read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} {
 		process_info_size: u32
 		process_info: win32.PROCESS_BASIC_INFORMATION
 		status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
@@ -143,25 +149,26 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			// TODO(flysand): There's probably a mismatch between NTSTATUS and
 			// windows userland error codes, I haven't checked.
 			err = Platform_Error(status)
-			return
-		}
-		if process_info.PebBaseAddress == nil {
-			// Not sure what the error is
-			err = General_Error.Unsupported
-			return
+			break read_peb
 		}
+		assert(process_info.PebBaseAddress != nil)
 		process_peb: win32.PEB
-
-		_ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
-
+		_, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb)
+		if err != nil {
+			break read_peb
+		}
 		process_params: win32.RTL_USER_PROCESS_PARAMETERS
-		_ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
-
+		_, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params)
+		if err != nil {
+			break read_peb
+		}
 		if selection >= {.Command_Line, .Command_Args} {
 			TEMP_ALLOCATOR_GUARD()
 			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
-			_ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
-
+			_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
+			if err != nil {
+				break read_peb
+			}
 			if .Command_Line in selection {
 				info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
 				info.fields += {.Command_Line}
@@ -175,23 +182,33 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 			TEMP_ALLOCATOR_GUARD()
 			env_len := process_params.EnvironmentSize / 2
 			envs_w := make([]u16, env_len, temp_allocator()) or_return
-			_ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
-
+			_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
+			if err != nil {
+				break read_peb
+			}
 			info.environment = _parse_environment_block(raw_data(envs_w), allocator) or_return
 			info.fields += {.Environment}
 		}
 		if .Working_Dir in selection {
 			TEMP_ALLOCATOR_GUARD()
 			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
-			_ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
-
+			_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
+			if err != nil {
+				break read_peb
+			}
 			info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
 			info.fields += {.Working_Dir}
 		}
 	}
-
-	if .Username in selection {
-		info.username = _get_process_user(ph, allocator) or_return
+	read_username: if .Username in selection {
+		username: string
+		username, err = _get_process_user(ph, allocator)
+		if _, ok := err.(runtime.Allocator_Error); ok {
+			return
+		} else if err != nil {
+			break read_username
+		}
+		info.username = username
 		info.fields += {.Username}
 	}
 	err = nil
@@ -202,16 +219,16 @@ _process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator
 _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
 	pid := process.pid
 	info.pid = pid
-	defer if err != nil {
-		free_process_info(info, allocator)
-	}
-
 	// Data obtained from process snapshots
-	if selection >= {.PPid, .Priority} { // snap process
+	snapshot_process: if selection >= {.PPid, .Priority} {
 		entry, entry_err := _process_entry_by_pid(info.pid)
 		if entry_err != nil {
-			err = General_Error.Not_Exist
-			return
+			err = entry_err
+			if entry_err == General_Error.Not_Exist {
+				return
+			} else {
+				break snapshot_process
+			}
 		}
 		if .PPid in selection {
 			info.fields += {.PPid}
@@ -222,12 +239,19 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
 			info.priority = int(entry.pcPriClassBase)
 		}
 	}
-	if .Executable_Path in selection { // snap module
-		info.executable_path = _process_exe_by_pid(pid, allocator) or_return
+	snapshot_module: if .Executable_Path in selection {
+		exe_path: string
+		exe_path, err = _process_exe_by_pid(pid, allocator)
+		if _, ok := err.(runtime.Allocator_Error); ok {
+			return
+		} else if err != nil {
+			break snapshot_module
+		}
+		info.executable_path = exe_path
 		info.fields += {.Executable_Path}
 	}
 	ph := win32.HANDLE(process.handle)
-	if selection >= {.Command_Line, .Environment, .Working_Dir} { // need peb
+	read_peb: if selection >= {.Command_Line, .Environment, .Working_Dir} {
 		process_info_size: u32
 		process_info: win32.PROCESS_BASIC_INFORMATION
 		status := win32.NtQueryInformationProcess(ph, .ProcessBasicInformation, &process_info, size_of(process_info), &process_info_size)
@@ -237,23 +261,24 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
 			err = Platform_Error(status)
 			return
 		}
-		if process_info.PebBaseAddress == nil {
-			// Not sure what the error is
-			err = General_Error.Unsupported
-			return
-		}
-
+		assert(process_info.PebBaseAddress != nil)
 		process_peb: win32.PEB
-		_ = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb) or_return
-
+		_, err = read_memory_as_struct(ph, process_info.PebBaseAddress, &process_peb)
+		if err != nil {
+			break read_peb
+		}
 		process_params: win32.RTL_USER_PROCESS_PARAMETERS
-		_ = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params) or_return
-
+		_, err = read_memory_as_struct(ph, process_peb.ProcessParameters, &process_params)
+		if err != nil {
+			break read_peb
+		}
 		if selection >= {.Command_Line, .Command_Args} {
 			TEMP_ALLOCATOR_GUARD()
 			cmdline_w := make([]u16, process_params.CommandLine.Length, temp_allocator()) or_return
-			_ = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w) or_return
-
+			_, err = read_memory_as_slice(ph, process_params.CommandLine.Buffer, cmdline_w)
+			if err != nil {
+				break read_peb
+			}
 			if .Command_Line in selection {
 				info.command_line = win32_utf16_to_utf8(cmdline_w, allocator) or_return
 				info.fields += {.Command_Line}
@@ -263,28 +288,37 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
 				info.fields += {.Command_Args}
 			}
 		}
-
 		if .Environment in selection {
 			TEMP_ALLOCATOR_GUARD()
 			env_len := process_params.EnvironmentSize / 2
 			envs_w := make([]u16, env_len, temp_allocator()) or_return
-			_ = read_memory_as_slice(ph, process_params.Environment, envs_w) or_return
-
+			_, err = read_memory_as_slice(ph, process_params.Environment, envs_w)
+			if err != nil {
+				break read_peb
+			}
 			info.environment =  _parse_environment_block(raw_data(envs_w), allocator) or_return
 			info.fields += {.Environment}
 		}
-
 		if .Working_Dir in selection {
 			TEMP_ALLOCATOR_GUARD()
 			cwd_w := make([]u16, process_params.CurrentDirectoryPath.Length, temp_allocator()) or_return
-			_ = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w) or_return
-
+			_, err = read_memory_as_slice(ph, process_params.CurrentDirectoryPath.Buffer, cwd_w)
+			if err != nil {
+				break read_peb
+			}
 			info.working_dir = win32_utf16_to_utf8(cwd_w, allocator) or_return
 			info.fields += {.Working_Dir}
 		}
 	}
-	if .Username in selection {
-		info.username = _get_process_user(ph, allocator) or_return
+	read_username: if .Username in selection {
+		username: string
+		username, err = _get_process_user(ph, allocator)
+		if _, ok := err.(runtime.Allocator_Error); ok {
+			return
+		} else if err != nil {
+			break read_username
+		}
+		info.username = username
 		info.fields += {.Username}
 	}
 	err = nil
@@ -294,15 +328,15 @@ _process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields
 @(private="package")
 _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) {
 	info.pid = get_pid()
-	defer if err != nil {
-		free_process_info(info, allocator)
-	}
-
-	if selection >= {.PPid, .Priority} { // snap process
+	snapshot_process: if selection >= {.PPid, .Priority} {
 		entry, entry_err := _process_entry_by_pid(info.pid)
 		if entry_err != nil {
-			err = General_Error.Not_Exist
-			return
+			err = entry_err
+			if entry_err == General_Error.Not_Exist {
+				return
+			} else {
+				break snapshot_process
+			}
 		}
 		if .PPid in selection {
 			info.fields += {.PPid}
@@ -313,14 +347,16 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
 			info.priority = int(entry.pcPriClassBase)
 		}
 	}
-	if .Executable_Path in selection {
+	module_filename: if .Executable_Path in selection {
 		exe_filename_w: [256]u16
 		path_len := win32.GetModuleFileNameW(nil, raw_data(exe_filename_w[:]), len(exe_filename_w))
+		assert(path_len > 0)
 		info.executable_path = win32_utf16_to_utf8(exe_filename_w[:path_len], allocator) or_return
 		info.fields += {.Executable_Path}
 	}
-	if selection >= {.Command_Line,  .Command_Args} {
+	command_line: if selection >= {.Command_Line,  .Command_Args} {
 		command_line_w := win32.GetCommandLineW()
+		assert(command_line_w != nil)
 		if .Command_Line in selection {
 			info.command_line = win32_wstring_to_utf8(command_line_w, allocator) or_return
 			info.fields += {.Command_Line}
@@ -330,14 +366,22 @@ _current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime
 			info.fields += {.Command_Args}
 		}
 	}
-	if .Environment in selection {
+	read_environment: if .Environment in selection {
 		env_block := win32.GetEnvironmentStringsW()
+		assert(env_block != nil)
 		info.environment = _parse_environment_block(env_block, allocator) or_return
 		info.fields += {.Environment}
 	}
-	if .Username in selection {
+	read_username: if .Username in selection {
 		process_handle := win32.GetCurrentProcess()
-		info.username = _get_process_user(process_handle, allocator) or_return
+		username: string
+		username, err = _get_process_user(process_handle, allocator)
+		if _, ok := err.(runtime.Allocator_Error); ok {
+			return
+		} else if err != nil {
+			break read_username
+		}
+		info.username = username
 		info.fields += {.Username}
 	}
 	if .Working_Dir in selection {