[C#/.NET] Starting Another Process And Writing To StandardInput (stdin)
bamccaig

I'm having trouble getting this to work and hope that one of you have done this before in .NET. The code I have seems like it should work, but unfortunately I can't directly test it because it's being executed by a subversion hook script (post-commit.bat)... :( Essentially, my program is like a wrapper for svn export. I'm thinking that what's happening is svn is prompting for confirmation to accept the SSL certificate, but since it's an automated process there is no user to respond. As a result, I'm attempting to simulate the user's response by writing to svn's stdin stream.

I'm starting svn with a System.Diagnostics.ProcessStartInfo instance and System.Diagnostics.Process.Start() as follows...

1using System;
2using System.Diagnostics;
3using System.IO;
4 
5.
6.
7.
8 
9int svn_timeout = (int)Math.Round(TimeSpan.FromMinutes(1).TotalMilliseconds, 0);
10 
11Process svn = null;
12ProcessStartInfo svn_startinfo = new ProcessStartInfo("svn", "export ...");
13 
14psi.RedirectStandardError = true;
15psi.RedirectStandardInput = true;
16psi.RedirectStandardOutput = true;
17psi.WorkingDirectory = Environment.CurrentDirectory;
18psi.UseShellExecute = false;
19 
20// Try to start svn.
21try
22{
23 // Start svn.
24 svn = Process.Start(svn_startinfo);
25}
26catch(Exception)
27{
28 // Print/log error and terminate with -1 exit status.
29}
30 
31// Wait for svn to exit.
32if(!svn.WaitForExit(svn_timeout))
33{
34 // Print/log error and terminate with -1 exit status.
35}
36 
37// Check svn exit status.
38if(svn.ExitCode != 0)
39{
40 // Print/log error and terminate with -1 exit status.
41}
42 
43// Print/log success.

I think it should work to WriteLine() a response to the StandardInput StreamWriter of the svn process.

1// |<--- svn = Process.Start()
2 
3/*
4 * I'd like to change this to only write if svn prompts. That should probably
5 * involve reading from svn.StandardOutput and looking for a particular message,
6 * which I don't currently have because my system already accepted the certificate.
7 * There is a WaitForInputIdle() method of the Process class, but the summary
8 * suggests that it is for GUI applications only.
9 */
10try
11{
12 // (t)emporarily accept certificate.
13 svn.StandardInput.WriteLine("t");
14 
15 // Flush buffer to stream.
16 svn.StandardInput.Flush();
17}
18catch(Exception)
19{
20 // Print/log error and terminate with -1 exit status.
21}
22 
23// svn.WaitForExit() --->|

I've added log messages before and after the above code where I write to the stdin stream. Both are logged, but nothing more. If svn fails to start, times out, or exits with a non-zero exit status that should be logged. If svn succeeds that should be logged. Instead, I get nothing to stderr, stdout, and nothing more to my log file. And my process just seems to hang there doing nothing.

I wonder if maybe the way I'm launching svn is wrong. Useful posts on the matter seem few and far between, but I did find one that suggested that the application being written to was terminating itself before the write could happen... Maybe something about the way I'm starting svn tells it to terminate if it starts to wait...

Does anybody have experience with this in .NET?

CGamesPlay
Quote:

I'm thinking that what's happening is svn is prompting for confirmation to accept the SSL certificate, but since it's an automated process there is no user to respond. As a result, I'm attempting to simulate the user's response by writing to svn's stdin stream.

If this really is the case, then the better solution is to permanently accept the certificate.

Quote:

That should probably involve reading from svn.StandardOutput and looking for a particular message, which I don't currently have because my system already accepted the certificate.

Remove your %APPDATA%\​Subversion directory.

Your best best would be to log on as the user that the subversion server is running as, permanently accept the certificate, and be done with it.

[append]

After experimentation, it seems that the certificate is stored in %APPDATA%\​Subversion\​auth\​svn.ssl.server\​<hash>. Using that information, you should be able to copy the certificate into the target configuration.

bamccaig
CGamesPlay said:

If this really is the case, then the better solution is to permanently accept the certificate.

...

Your best best would be to log on as the user that the subversion server is running as, permanently accept the certificate, and be done with it.

I agree, that is an excellent solution, but unfortunately I hit another snag there. I don't know why, but my Subversion hook (which is executing my program, which then executes svn) is being executed as a "phantom" user (and I just made that up so it has no specific meaning). The USERNAME environment variable is apparently an empty string. I haven't figured that out yet, since every workstation I've tested (as opposed to the development server) has an actual user executing it... :-/ So it's not going to be quite so easy, but I would certainly like to figure out why the USERNAME variable is empty... :-/ Does anybody know where the Subversion hook scripts get their user credentials from? :-/

CGamesPlay said:

After experimentation, it seems that the certificate is stored in %APPDATA%\​Subversion\​auth\​svn.ssl.server\​<hash>. Using that information, you should be able to copy the certificate into the target configuration.

That sounds like a nice solution, but unfortunately the APPDATA environment variable is also empty when the hook script is executed. :( So I don't think Subversion is creating any kind of user data...

CGamesPlay

So it's running as NETWORK SERVICE or NETWORK GUEST or some such... Perhaps you can create the configuration in All Users\Subversion and then set the environment variable yourself? Sounds easier.

Oh, and subversion hook scripts run under the subversion server process. If that's IIS, then it's going to be the NETWORK SERVICE or NETWORK GUEST accounts.

bamccaig

We're running VisualSVN Server, which AFAIK is using Apache. :-/ Personally, I wanted to just install Subversion and Apache myself...

CGamesPlay

Here's one:

svn export --config-dir=C:\svnconfig ...

Got that out of svn help export. Should fix you up, right?

Also, there's a --non-interactive option, but that will probably cause it to fail silently.

bamccaig

I was looking at that, but I wasn't sure the --config-dir is the %APPDATA%\Subversion equivalent... I tried searching for confirmation, but couldn't find it... In hindsight I suppose it should be obvious, but testing is difficult and abstract through a hook script... :-X Plus I've been pretty tired these past few days.

CGamesPlay

So, does it work?

bamccaig

I can't say... I'm no longer at work and I'm honestly too tired to test it right now...

CGamesPlay

Yeah, so I'm going to keep bumping this thread so when it doesn't work you can just reply instead of starting a new thread :P

bamccaig

Heh, I'm now at work... Now I need to figure out how to go about testing this...

** EDIT **

It's taking longer than expected. Was having problems getting the service process to run as a custom user with restricted access and ownership of the desired directories. Hopefully I can confirm whether this --config-dir option solves my problem in the next hour.

I'm still curious why writing to stdin didn't work though... :-/ It was a poor solution to this problem, but it would be nice to understand how it works to write to stdin of another process like that for future reference.

** EDIT **

It just occurred to me to just try svn log on my local machine with a new --config-dir option specified to see if it would create the directory and prompt me again for authentication and the SSL certificate. It did so I'm pretty confident that this solution will work... At least, if nothing else goes wrong. :P

CGamesPlay
Quote:

I'm still curious why writing to stdin didn't work though... :-/ It was a poor solution to this problem, but it would be nice to understand how it works to write to stdin of another process like that for future reference.

On Linux, password prompts do not read from stdin: instead, they read from the owner terminal. This means you can't echo password | ssh host. Something similar may be going on for subversion.

bamccaig

I'm unsure if the config directory is working or not... svn appears to be still prompting for confirmation, which doesn't seem to make sense because if a human user executes an svn command while pointing to the created Subversion client config directory they are not prompted to accept the certificate... :'(

CGamesPlay said:

On Linux, password prompts do not read from stdin: instead, they read from the owner terminal. This means you can't echo password | ssh host. Something similar may be going on for subversion.

Well I wasn't actually writing the password to svn's stdin. Instead, I was writing a 't' character to temporarily accept the SSL certificate, which I just confirmed works from the command line in Windows by piping it.

svn log url
<svn prompt for confirmation>t
<output>
svn log url
<svn prompt for confirmation>t
<output>
echo t | svn log url
<output>

What exactly is the difference between reading from the owner terminal and stdin? I think that I was under the impression that the owner terminal would be writing to stdin, though I admit that my understanding of terminals is abstract and incomplete... :-/

CGamesPlay

Pipe the output of the svn export to a temporary file so you can see what exactly it is outputting. Also, have you used --non-interactive?

Quote:

What exactly is the difference between reading from the owner terminal and stdin?

One can be redirected to a file, and the other is the terminal? :P I don't know if Windows works the same way, but it probably doesn't.

bamccaig

It seems to be working (at least in part) to specify the --config-dir option, but now I'm getting the following output to svn's stderr stream (by memory):

Authentication realm: (url) authentication_realm_name

It doesn't really resemble a prompt, but there is no further output and the program seems to hang there... When a colleague with server access executes the command from the Command Prompt it executes successfully and the above message isn't displayed (unless stderr isn't display by default in Windows, in which case I don't know if this message is written to stderr for the colleague). If it's not a prompt, then my only explanation is that it's actually just Apache being informative and my debugging code is blocking...

// Debugging code to try to see why/if svn is failing.

string line;

Program.StartLog("svn stderr:\n\n");
while((line = svn.StandardError.ReadLine()) != null)
  Program.Log(String.Format("{0}\n", line));

Program.StartLog("svn stdout:\n\n");
while((line = svn.StandardOutput.ReadLine()) != null)
  Program.Log(String.Format("{0}\n", line));

Does anybody know if this is the right/wrong way to read from the streams in C#? I've noted in examples that it seems the Flush() or Close() method needs to be called for some reason, but I don't really understand how you would go about reading again (as some programs would dictate) if the stream is closed... :-/ I don't really understand how to check if there is data to be read from a particular StreamReader; if so read it line by line and process it; and if not continue on with the program...

** EDIT **

I removed the above code from the program (I think) and it's still blocking on the svn process... :-/ I can't imagine any reason for svn to take more than a second to execute (it's only processing one file) unless it's prompting...

CGamesPlay said:

Pipe the output of the svn export to a temporary file so you can see what exactly it is outputting.

I'm not sure how I would do that in C#... :-/

CGamesPlay said:

Also, have you used --non-interactive?

I originally wanted to use that option, but before I understood the --config-dir option I was trying to figure out how to respond to a prompt... I guess now I can go back to --non-interactive mode and hope that svn either succeeds or writes something useful to stderr.

CGamesPlay
Quote:

I'm not sure how I would do that in C#... :-/

You would not use C#, you would use the shell's output piping: command > program.log

bamccaig
CGamesPlay said:

You would not use C#, you would use the shell's output piping: command > program.log

Are you talking about svn's output when executed by my program or by my colleague from the command line? :-/ My colleague copied the output from svn to me and it was the standard successful output...

prompt>svn export arguments flags file Export complete.

From my program, svn is started with System.Diagnostics.Process.Start(ProcessStartInfo). The ProcessStartInfo instance has a FileName property (i.e. the executable) and an Arguments property. I'm not sure if it would work to add redirection arguments (especially with the UseShellExecute property set to false)...

I did try reading from the svn output streams and writing them to files:

1StreamWriter svn_stderr = null;
2StreamWriter svn_stdout = null;
3 
4try
5{
6 svn_stderr = new StreamWriter(@".\svn.stderr");
7 svn_stderr.Write(svn.StandardError.ReadToEnd());
8}
9catch(Exception)
10{
11 Program.PrintError(msg = "Failed to redirect svn stderr.\n");
12 Program.StartLog(msg);
13}
14finally
15{
16 svn_stderr.Close();
17 svn_stderr = null;
18}
19 
20try
21{
22 svn_stdout = new StreamWriter(@".\svn.stdout");
23 svn_stdout.Write(svn.StandardOutput.ReadToEnd());
24}
25catch(Exception)
26{
27 Program.PrintError(msg = "Failed to redirect svn stdout.\n");
28 Program.StartLog(msg);
29}
30finally
31{
32 svn_stdout.Close();
33 svn_stdout = null;
34}

Which seems to work on my local system, but is apparently never reached on the server... :-/ If there was an uncaught exception (while being executed by a user with no logon) what should happen to my program...? Would Windows terminate it or would it just stop executing and hang?

CGamesPlay

You have a batch file, called post-commit.bat. In it, you run svn export <parameters> > C:\WINDOWS\Temp\post-commit.log You then review post-commit.log and find why your stuff doesn't work.

bamccaig
CGamesPlay said:

You have a batch file, called post-commit.bat. In it, you run svn export <parameters> > C:\WINDOWS\Temp\post-commit.log You then review post-commit.log and find why your stuff doesn't work.

That's brilliant! :o I can't believe I didn't think of running svn from the batch file... :-[

MiquelFire

Didn't I say that in another one of your threads? ???

bamccaig
MiquelFire said:

Didn't I say that in another one of your threads? ???

I'm not sure... ??? Not that I can find... You did suggest that I call a more powerful script from my batch file, but instead I've opted to write a simple C# program to handle it. That's all I can find...

MiquelFire

I think the more powerful program was meant for if you need to delete a file, as if you delete/rename in SVN, the exported copy would still have the last version, and if it's a file that needs to be deleted...

bamccaig

By executing svn from the batch file, the following was output to stderr:

svn: PROPFIND request failed on 'file' svn: PROPFIND of 'file': authorization failed (url)

stdout was empty. :-/

BAF

Personally, I would check out a working copy, use htaccess do deny any access to anything .svn, and then just issue a svn update.

CGamesPlay

You used --config-dir and --non-interactive? You should run it manually and add in the user and password information, since it seems it couldn't ask you for them.

bamccaig
BAF said:

Personally, I would check out a working copy, use htaccess do deny any access to anything .svn, and then just issue a svn update.

You're assuming the Web server is Apache. :)

CGamesPlay said:

You used --config-dir and --non-interactive? You should run it manually and add in the user and password information, since it seems it couldn't ask you for them.

Yes, both options were used. The user authentication information should already be cached... :-/ A colleague with server access can run svn export with the --config-dir option and it executes successfully without any prompts... :-/

** EDIT **

Executing the exact same svn export command as the batch file (with the --non-interactive option)(and to be paranoid, my colleague's personal "%APPDATA%\Subversion" directory deleted), she is getting the same stderr output as the hook script.

** EDIT **

Without the --non-interactive option apparently it just sits there... :-/

svn export --force --config-dir "directory" "file_url" "file_path" \ 1>"svn-stdout.txt" 2>"svn-stderr.txt"

** EDIT **

As an administrator, it executes fine. :-/

** EDIT **

Actually, svn wasn't just sitting there... It was waiting for her to enter the password. The username/password prompt was being redirected to files... :-[

CGamesPlay said:

You should run it manually and add in the user and password information, since it seems it couldn't ask you for them.

You appear to be correct... I thought we already saved the user credentials, but apparently not... So we're doing it again now... I'm about to find out if it worked...

** EDIT **

Now the hook script fails and none of the stream files exist. :-/

BAF

Run it as the user it will be run as manually, without the redirection.

bamccaig
BAF said:

Run it as the user it will be run as manually, without the redirection.

I don't even know if that's possible... The user has no logon, whatever that means... :-/

Anyway, it seems that when I sent the latest revision of my program to replace the currently "installed" one I sent it with a .txt extension appended. My colleague forgot to rename it so the batch file wasn't able to find my program, which caused it to fail and no output to be captured... After correcting that, I got log messages indicating that svn exited with a non-zero exit status. When I left work I had just modified the program to capture svn's output. Tomorrow I'll see if that yields anything useful. :-/

BAF

You can still run it as that user.

CGamesPlay

Just so we're clear, the only purpose of the C# program is to run svn export? And you are running the C# program from a batch file? If that is actually the case, then what you are doing is almost worse-than-failure-worthy.

On the topic of actually making the solution work: if it's asking for a user name and password, provide one. The commands are quite clearly shown in svn help export.

bamccaig
BAF said:

You can still run it as that user.

IIRC, I asked my colleague to do that and either it failed because "the user account doesn't have a logon" or my colleague didn't know how to do it (or I misinterpreted her)... :-/

CGamesPlay said:

Just so we're clear, the only purpose of the C# program is to run svn export? And you are running the C# program from a batch file? If that is actually the case, then what you are doing is almost worse-than-failure-worthy.

The C# program only exports files that have changed in the most recent commit (as well as creating directories and deleting files/directories) as indicated by svnlook changed. There are thousands files which as you can expect would take a long time to export each time. Obviously svn update would be a nice, simple solution, but then you have the .svn directories which are undesired, especially on remote, third party servers. There may be better ways to do what I'm doing, but this is the solution I've come up with... :-/

BAF

Just hide the .svn directories. Can't be that hard, no matter what you're using.

bamccaig
BAF said:

Just hide the .svn directories. Can't be that hard, no matter what you're using.

I tried to Google it and came up mostly empty... :-/ Anybody know how to configure IIS to NOT serve directories based on name alone? I don't know if the higher ups would go for this anyway so it's only a partial/alternative solution. I've still gotta get my program to interface properly with Subversion.

CGamesPlay

How about just do an svn update locally and then copy all the files that aren't named ".svn"?

bamccaig
CGamesPlay said:

How about just do an svn update locally and then copy all the files that aren't named ".svn"?

We'd rather an automated process... It should be more reliable and less error-prone (I hope).

** EDIT **

The output from svn, when executed by my program/hook script, is...

svn: PROPFIND request failed on 'relative_file_path' svn: PROPFIND of 'relative_file_path': authorization failed (url)

My colleague can run the same command fine without any prompts or errors. WTF!? >:(:'(

CGamesPlay
Quote:

We'd rather an automated process... It should be more reliable and less error-prone (I hope).

It's pretty easy: you already have this:

Quote:

The C# program only exports files that have changed in the most recent commit (as well as creating directories and deleting files/directories) as indicated by svnlook changed.

So just takes those files, and copy them using the C# program, instead of using svn export. Maybe a mapped network drive, maybe FTP, maybe WebDAV, whatever you like.

bamccaig
CGamesPlay said:

So just takes those files, and copy them using the C# program, instead of using svn export. Maybe a mapped network drive, maybe FTP, maybe WebDAV, whatever you like.

Copy them from where? :-/ They're in a Subversion repository until they get exported out... I'm not sure I understand what you're suggesting. If my colleague manually executes the hook script with the same arguments that Subversion passes it, it works flawlessly. It seems the only problem is with user permissions on the server. :-/

CGamesPlay

Maybe you think that svn export can store the files on a remote destination? It can't.

I'm going to start from scratch. You have a subversion repository and a web server. These are on different machines. You need the web server to be updated when the subversion repository is updated.

I would solve this problem using a post-commit hook, like you are trying to do. The post commit hook obviously runs on the machine with the subversion repository.

The script should maintain a local copy of the repository (updated with svn update). When the script is executed, it should update the repository, then copy all of the files in the local repository to the remote server, ignoring the subversion special directories.

As an example, this would be the script, using lftp to send the files over unencrypted FTP.

cd C:\subversion\www_files
svn up
lftp ftp://user:password@host -e "mirror -nvR --delete -x \.svn . ."

This is based on a script I use to update a static HTML web site that uses a subversion repository to maintain its files. The script only updates files that have changed, as based on the time stamp and file size. The update process is slow if there are a large number of directories in the repository (lftp must look in each one on the remote server).

BAF

If you can access the files locally (mapped network drive or something), instead of using FTP, you could use rsync, might be faster...

bamccaig

Well the server is running Windows Server and the Subversion server and Web server are both running on the same machine with "local access" to the repositories and document roots. I need a Windows solution and I'm pretty sure I was told that Cygwin-/MSYS-like tools are not an option. :-/

CGamesPlay

Have you tried using svn export using the local paths then? Obviously this isn't the solution you should use in the long-term, but does it work?

If you have access to the wwwroot locally, you can replace lftp with xcopy. It's seriously the easiest thing ever, after I just tried it.

xcopy can recursively (/E) copy a directory and by default ignores hidden files (.svn directories are hidden). xcopy can also be made to only copy files with the archive bit set (/M; the archive bit is set by writing to the file). It can be forced with /Y. xcopy also has a decent file exclusion pattern, but it won't be needed if all you want to do is ignore the hidden files and directories. So, the solution:

REM assumes that a checked out repository is in C:\inetpub\trunk
REM and that the webroot is C:\inetpub\wwwroot
cd C:\inetpub
svn up trunk
xcopy /M /E /Y trunk wwwroot

By using /M, xcopy will copy all files that have been written, then clear the archive bit, so only changed files will be written after that point. Note that this will not handle deletion of files.

bamccaig
CGamesPlay said:

Have you tried using svn export using the local paths then? Obviously this isn't the solution you should use in the long-term, but does it work?

No, I hadn't. :-[ It works fine with a file:/// url. Why isn't this a solution to use in the long-term though? :-/ Am I missing something?

CGamesPlay

If you are using subversion export with an https repository URL and a local path for the local url, then you should be fine. I think the svn up idea is a better solution though.

bamccaig

Currently, I'm using subversion export with a file repository URL (file:///) and a local path (<tt><DriveLetter>:</tt>). Is there anything particularly wrong with it (i.e. security holes or performance)? :-/

The svn up idea does sound better, but it would still need a wrapper of some sort to send particular branches to particular web roots (i.e. current development branch, current testing branch) automatically when they are committed. As is, a simple configuration file is used that matches branches to web roots... :-/ That was pretty much why I moved it into a C# program. :-/

CGamesPlay
Quote:

Is there anything particularly wrong with it (i.e. security holes or performance)? :-/

I'm going to go with file locking issues because you are having two processes access the database at the same time. It is probably fine, but personally I'd prefer to use the web URL if it were possible.

Quote:

As is, a simple configuration file is used that matches branches to web roots... :-/ That was pretty much why I moved it into a C# program. :-/

Yeah, I figured that was what was going on, but you were being pretty vague :)

Crazy Photon

Have you tried saving the users in a file and then redirect stdin to the execution command?

svn .... < bleh.txt

bamccaig
Crazy Photon said:

Have you tried saving the users in a file and then redirect stdin to the execution command?

svn .... < bleh.txt

I think we've already solved the problem you're offering solutions for. :-/ Thanks though. :) Currently, the only problem appears the be the inability to use an https:// url for the repository. It seems to work with a file:/// url, but for various reasons I'd prefer to get https:// working. :-/ I assume the reason https:// doesn't work is because the user account running the Subversion server (which executes the hook script) has limited permissions, but I don't know much about Windows Server, its user accounts, or the actual user's configuration. :-/

aj5555

i do this process/stdin stuff all the time with just plain C.

Thread #596335. Printed from Allegro.cc