/// <summary> /// Initialise the service to listen for reports. /// </summary> public WebHandler() { try { FileReceiptPath = Properties.Settings.Default.CrashReportRepository + "-Temp"; LandingZone = new LandingZoneMonitor( Properties.Settings.Default.CrashReportRepository, CrashReporterReceiverServicer.Log ); Directory.CreateDirectory(FileReceiptPath); // Fire up a listener. ServiceHttpListener = new HttpListener(); ServiceHttpListener.Prefixes.Add("http://*:57005/CrashReporter/"); ServiceHttpListener.Start(); ServiceHttpListener.BeginGetContext(AsyncHandleHttpRequest, null); bStartedSuccessfully = true; } catch (Exception Ex) { CrashReporterReceiverServicer.WriteEvent("Initialisation error: " + Ex.ToString()); } }
/// <summary> /// Save out the current list of received reports to disk /// </summary> void MemorizeReceivedReports() { var MemoPath = Path.Combine(Directory, MemoFilename); // Recreate the landing zone directory, just in case. System.IO.Directory.CreateDirectory(Directory); try { if (Interlocked.Exchange(ref MemoBeingWritten, 1) == 1) { return; } new XElement ( "Root", from Item in ReceivedReports.OrderBy(x => x.Value) select new XElement("CrashReport", Item.Key, new XAttribute("time", Item.Value.ToString())) ) .Save(MemoPath); CrashReporterReceiverServicer.WriteEvent("Saved to " + MemoPath); Interlocked.Exchange(ref MemoBeingWritten, 0); } catch (Exception Ex) { LogFile.Print("Failed to memorize received reports: " + Ex.Message); Interlocked.Exchange(ref MemoBeingWritten, 0); } }
private void PoolPerformanceData() { while (true) { Int64 NumTotalFiles = NumFilesReceived + NumFilesSkipped; // Get the CPU for the last second. CurrentCPUUsage = PerfCounterProcessorTime.NextValue(); if (CurrentReceivedData > 0 && CurrentTotalUploadCount > 0) { CrashReporterReceiverServicer.WritePerf ( string.Format("CPU Usage: {0:00}%, Received data: {1,5}MB/{2:00.00} of {4:00.00} MB, Uploads:{3,3}, Files:{5,6},{6,6} : {7:00}%", CurrentCPUUsage, (Int64)Math.Truncate(MBInv * TotalReceivedData), MBInv * CurrentReceivedData, UploadCount, MBInv * MaxAllowedBandwidth, NumFilesReceived, NumFilesSkipped, NumTotalFiles > 0 ? 100.0f * NumFilesSkipped / NumTotalFiles : 0.0f) ); } Interlocked.Add(ref TotalReceivedData, CurrentReceivedData); // Reset the the performance data. Interlocked.Exchange(ref CurrentReceivedData, 0); Interlocked.Exchange(ref CurrentTotalUploadCount, 0); // Sleep. System.Threading.Thread.Sleep(1000); } }
/// <summary> /// Periodically delete folders of abandoned reports /// </summary> void CheckForAbandonedReports() { if (Interlocked.Exchange(ref CheckingForAbandonedReports, 1) == 1) { // Being checked by another thread, so no need return; } try { var Now = DateTime.Now; // No need to do this very often if ((Now - LastAbandonedReportCheckTime).Minutes < MinimumMinutesBetweenAbandonedReportChecks) { return; } LastAbandonedReportCheckTime = Now; try { foreach (string Dir in Directory.EnumerateDirectories(FileReceiptPath)) { var LastAccessTime = new DateTime(); string ReportId = new DirectoryInfo(Dir).Name; bool bGotAccessTime = UploadsInProgress.TryGetLastAccessTime(ReportId, ref LastAccessTime); if (!bGotAccessTime || (Now - LastAccessTime).Minutes > AgeMinutesToConsiderReportAbandoned) { try { Directory.Delete(Dir, true /* delete all contents */); } catch (Exception Ex) { CrashReporterReceiverServicer.WriteEvent(string.Format("Failed to delete directory {0}: {1}", ReportId, Ex.Message)); } } } } catch (Exception Ex) { // If we get in here, the reports won't get cleaned up. May happen if // permissions are incorrect or the directory is locked CrashReporterReceiverServicer.WriteEvent("Error during folder tidy: " + Ex.Message); } } finally { // Allow other threads to check again Interlocked.Exchange(ref CheckingForAbandonedReports, 0); } }
/// <summary> /// The main entry point for crash report receiver application. /// </summary> static void Main() { CrashReporterReceiverServicer CrashReporterReceiver = new CrashReporterReceiverServicer(); if( !Environment.UserInteractive ) { // Launch the service as normal if we aren't in the debugger, or aren't in an interactive environment ServiceBase.Run( CrashReporterReceiver ); } else { // Call OnStart, wait for a console key press, call OnStop CrashReporterReceiver.DebugRun(); } }
/// <summary> /// Check to see if we wish to reject a report based on the WER meta data. /// </summary> /// <param name="Request">A request containing the XML representation of a WERReportMetadata class instance.</param> /// <returns>true if we do not reject.</returns> private CrashReporterResult CheckReportDetail(HttpListenerRequest Request) { CrashReporterResult ReportResult = new CrashReporterResult(); #if DISABLED_CRR ReportResult.bSuccess = false; ReportResult.Message = "CRR disabled"; CrashReporterReceiverServicer.WriteEvent("CheckReportDetail() Report rejected by disabled CRR"); #else string WERReportMetadataString = GetContentStreamString(Request); WERReportMetadata WERData = null; if (WERReportMetadataString.Length > 0) { try { WERData = XmlHandler.FromXmlString <WERReportMetadata>(WERReportMetadataString); } catch (System.Exception Ex) { CrashReporterReceiverServicer.WriteEvent("Error during XmlHandler.FromXmlString, probably incorrect encoding, trying to fix: " + Ex.Message); byte[] StringBytes = System.Text.Encoding.Unicode.GetBytes(WERReportMetadataString); string ConvertedXML = System.Text.Encoding.UTF8.GetString(StringBytes); WERData = XmlHandler.FromXmlString <WERReportMetadata>(ConvertedXML); } } if (WERData != null) { // Ignore crashes in the minidump parser itself ReportResult.bSuccess = true; if (WERData.ProblemSignatures.Parameter0.ToLower() == "MinidumpDiagnostics".ToLower()) { ReportResult.bSuccess = false; ReportResult.Message = "Rejecting MinidumpDiagnostics crash"; } // Ignore Debug and DebugGame crashes string CrashingModule = WERData.ProblemSignatures.Parameter3.ToLower(); if (CrashingModule.Contains("-debug")) { ReportResult.bSuccess = false; ReportResult.Message = "Rejecting Debug or DebugGame crash"; } } #endif return(ReportResult); }
/// <summary> /// The main entry point for crash report receiver application. /// </summary> static void Main() { CrashReporterReceiverServicer CrashReporterReceiver = new CrashReporterReceiverServicer(); if (!Environment.UserInteractive) { // Launch the service as normal if we aren't in the debugger, or aren't in an interactive environment ServiceBase.Run(CrashReporterReceiver); } else { // Call OnStart, wait for a console key press, call OnStop CrashReporterReceiver.DebugRun(); } }
/// <summary> /// Check to see if a report has already been uploaded. /// </summary> /// <param name="Request">A request containing either the Report Id as a string or an XML representation of a CheckReportRequest class instance.</param> /// <returns>Result object, indicating whether the report has already been uploaded.</returns> private CrashReporterResult CheckReport(HttpListenerRequest Request) { var ReportResult = new CrashReporterResult(); var RequestClass = new CheckReportRequest(); RequestClass.ReportId = GetReportIdFromPostData(GetContentStreamString(Request)); ReportResult.bSuccess = !LandingZone.HasReportAlreadyBeenReceived(RequestClass.ReportId); if (!ReportResult.bSuccess) { CrashReporterReceiverServicer.WriteEvent(string.Format("Report \"{0}\" has already been received", RequestClass.ReportId)); } return(ReportResult); }
/// <summary> /// Check to see if a report has already been uploaded. /// </summary> /// <param name="Request">A request containing either the Report Id as a string or an XML representation of a CheckReportRequest class instance.</param> /// <returns>Result object, indicating whether the report has already been uploaded.</returns> private CrashReporterResult CheckReport(HttpListenerRequest Request) { CrashReporterResult ReportResult = new CrashReporterResult(); #if DISABLED_CRR ReportResult.bSuccess = false; CrashReporterReceiverServicer.WriteEvent("CheckReport() Report rejected by disabled CRR"); #else var RequestClass = new CheckReportRequest(); RequestClass.ReportId = GetReportIdFromPostData(GetContentStreamString(Request)); ReportResult.bSuccess = !LandingZone.HasReportAlreadyBeenReceived(RequestClass.ReportId); if (!ReportResult.bSuccess) { CrashReporterReceiverServicer.WriteEvent(string.Format("Report \"{0}\" has already been received", RequestClass.ReportId)); } #endif return(ReportResult); }
/// <summary> /// Move a successfully uploaded WER into the landing zone folder /// </summary> /// <param name="SourceDirInfo">Temporary directory WER was uploaded to</param> /// <param name="ReportId">Unique ID of the report, used as the folder name in the landing zone</param> public void ReceiveReport(DirectoryInfo SourceDirInfo, string ReportId) { string ReportPath = Path.Combine(Directory, ReportId); // Recreate the landing zone directory, just in case. System.IO.Directory.CreateDirectory(Directory); DirectoryInfo DirInfo = new DirectoryInfo(ReportPath); if (DirInfo.Exists) { DirInfo.Delete(true); DirInfo.Refresh(); } // Retry the move in case the OS keeps some locks on the uploaded files longer than expected for (int Retry = 0; Retry != MoveUploadedReportRetryAttempts; ++Retry) { try { SourceDirInfo.MoveTo(DirInfo.FullName); break; } catch (Exception) { } System.Threading.Thread.Sleep(100); } CrashReporterReceiverServicer.WriteEvent("A new report has been received into " + DirInfo.FullName); ReceivedReports[ReportId] = DateTime.Now; // Ensure the receive reports memo file doesn't grow indefinitely ForgetOldReports(); // Update file on disk, in case service is restarted MemorizeReceivedReports(); }
/// <summary> Spin loop for a while, maybe we hit an opportunity to start the upload. </summary> private bool TryWaitForStartOperation() { bool bCPUExceeded = false; bool bBandwidthExceeded = false; for (int Index = 0; Index < 4; Index++) { Thread.Sleep(1000); bCPUExceeded = CurrentCPUUsage > MaxAllowedCPUtilizationPct; bBandwidthExceeded = CurrentReceivedData > MaxAllowedBandwidth; if (!bCPUExceeded && !bBandwidthExceeded) { // Found, continue. CrashReporterReceiverServicer.WritePerf("TryWaitForStartOperation was successful"); return(true); } } // Give up. Interlocked.Increment(ref NumFilesSkipped); return(false); }
/// <summary> /// The main listener callback to handle client requests. /// </summary> /// <param name="ClientRequest">The request from the client.</param> private void AsyncHandleHttpRequest(IAsyncResult ClientRequest) { try { HttpListenerContext Context = ServiceHttpListener.EndGetContext(ClientRequest); ServiceHttpListener.BeginGetContext(AsyncHandleHttpRequest, null); HttpListenerRequest Request = Context.Request; bool bIgnorePerfData = false; using (HttpListenerResponse Response = Context.Response) { // Extract the URL parameters string[] UrlElements = Request.RawUrl.Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); // http://*:57005/CrashReporter/CheckReport // http://*:57005/CrashReporter/CheckReportDetail CrashReporterResult ReportResult = new CrashReporterResult(); if (UrlElements[0].ToLower() == "crashreporter") { switch (UrlElements[1].ToLower()) { case "ping": ReportResult.bSuccess = true; break; case "checkreport": ReportResult = CheckReport(Context.Request); break; case "checkreportdetail": ReportResult = CheckReportDetail(Context.Request); break; case "uploadreportfile": ReportResult = ReceiveFile(Context.Request); bIgnorePerfData = true; break; case "uploadcomplete": ReportResult = UploadComplete(Context.Request); break; default: ReportResult.bSuccess = false; ReportResult.Message = "Invalid command: " + UrlElements[1]; break; } } else { ReportResult.bSuccess = false; ReportResult.Message = "Invalid application: " + UrlElements[0] + " (expecting CrashReporter)"; } string ResponseString = XmlHandler.ToXmlString <CrashReporterResult>(ReportResult); Response.SendChunked = true; Response.ContentType = "text/xml"; byte[] Buffer = Encoding.UTF8.GetBytes(ResponseString); Response.ContentLength64 = Buffer.Length; Response.OutputStream.Write(Buffer, 0, Buffer.Length); Response.StatusCode = ( int )HttpStatusCode.OK; if (!bIgnorePerfData) { // Update the overhead data. Int64 ContentLenght = Response.ContentLength64 + Request.ContentLength64; Interlocked.Add(ref UploadsInProgress.CurrentReceivedData, ContentLenght); } } } catch (Exception Ex) { CrashReporterReceiverServicer.WriteEvent("Error during async listen: " + Ex.Message); } }
/// <summary> /// Receive a file and write it to a temporary folder. /// </summary> /// <param name="Request">A request containing the file details in the headers (DirectoryName/FileName/FileLength).</param> /// <returns>true if the file is received successfully.</returns> /// <remarks>There is an arbitrary file size limit of CrashReporterConstants.MaxFileSizeToUpload as a simple exploit prevention method.</remarks> private CrashReporterResult ReceiveFile(HttpListenerRequest Request) { CrashReporterResult ReportResult = new CrashReporterResult(); if (!Request.HasEntityBody) { return(ReportResult); } // Take this opportunity to clean out folders for reports that were never completed CheckForAbandonedReports(); // Make sure we have a sensible file size long BytesToReceive = 0; if (long.TryParse(Request.Headers["FileLength"], out BytesToReceive)) { if (BytesToReceive >= CrashReporterConstants.MaxFileSizeToUpload) { return(ReportResult); } } string DirectoryName = Request.Headers["DirectoryName"]; string FileName = Request.Headers["FileName"]; var T = Request.ContentLength64; bool bIsOverloaded = false; if (!UploadsInProgress.TryReceiveFile(DirectoryName, FileName, BytesToReceive, ref ReportResult.Message, ref bIsOverloaded)) { CrashReporterReceiverServicer.WriteEvent(ReportResult.Message); ReportResult.bSuccess = false; return(ReportResult); } string PathName = Path.Combine(FileReceiptPath, DirectoryName, FileName); // Recreate the file receipt directory, just in case. Directory.CreateDirectory(FileReceiptPath); // Create the folder to save files to DirectoryInfo DirInfo = new DirectoryInfo(Path.GetDirectoryName(PathName)); DirInfo.Create(); // Make sure the file doesn't already exist. If it does, delete it. if (File.Exists(PathName)) { File.Delete(PathName); } FileInfo Info = new FileInfo(PathName); FileStream FileWriter = Info.OpenWrite(); // Read in the input stream from the request, and write to a file long OriginalBytesToReceive = BytesToReceive; try { using (BinaryReader Reader = new BinaryReader(Request.InputStream)) { byte[] Buffer = new byte[CrashReporterConstants.StreamChunkSize]; while (BytesToReceive > 0) { int BytesToRead = Math.Min((int)BytesToReceive, CrashReporterConstants.StreamChunkSize); Interlocked.Add(ref UploadsInProgress.CurrentReceivedData, BytesToRead); int ReceivedChunkSize = Reader.Read(Buffer, 0, BytesToRead); if (ReceivedChunkSize == 0) { ReportResult.Message = string.Format("Partial file \"{0}\" received", FileName); ReportResult.bSuccess = false; CrashReporterReceiverServicer.WriteEvent(ReportResult.Message); return(ReportResult); } BytesToReceive -= ReceivedChunkSize; FileWriter.Write(Buffer, 0, ReceivedChunkSize); } } } finally { FileWriter.Close(); Request.InputStream.Close(); UploadsInProgress.FileUploadAttemptDone(OriginalBytesToReceive, bIsOverloaded); bool bWriteMetadata = Path.GetExtension(FileName) == ".ue4crash"; if (bWriteMetadata) { string CompressedSize = Request.Headers["CompressedSize"]; string UncompressedSize = Request.Headers["UncompressedSize"]; string NumberOfFiles = Request.Headers["NumberOfFiles"]; string MetadataPath = Path.Combine(FileReceiptPath, DirectoryName, Path.GetFileNameWithoutExtension(FileName) + ".xml"); XmlHandler.WriteXml <FCompressedCrashInformation>(new FCompressedCrashInformation(CompressedSize, UncompressedSize, NumberOfFiles), MetadataPath); } } ReportResult.bSuccess = true; return(ReportResult); }