Opened 11 years ago

Closed 11 years ago

Last modified 11 years ago

#4008 closed Bug (invalid)

allow user to redirect stderr stdout separately from backgrounding

Reported by: Astara Owned by:
Priority: Normal Milestone: None Set
Component: Daemon Version: 2.20
Severity: Normal Keywords: config; logs
Cc:

Description

I found this very troublesome -- it's uncommon to have the 'background' option also redirect stdout & stderr to /dev/null, since if someone wanted to do that, they could on the command line and also have the flexibility of sending stdout and stderr to separate files (or ignoring one and not the other).

This code gets rid of the various cases and has them all call 1 background routine that will change dir to '/' (or not(arg)) and open stdin to /dev/null (or not(arg)) and puts transmission into a new sid.

Replaces the 3 alternate compiles with 1.

diff is against my latest work tree which is fairly current, but isn't trunk.

in $SRC/daemon:daemon.c

--- daemon.c.orig       2011-01-08 17:29:29.000000000 -0800
+++ daemon.c    2011-02-09 00:35:51.683859316 -0800
@@ -182,56 +182,27 @@
 #define USE_OS_DAEMON
 #endif
 
+
 static int tr_daemon (int nochdir, int noclose) {
-#if defined(USE_OS_DAEMON)
-  return daemon (nochdir, noclose);
-#elif defined(USE_TR_DAEMON)
-  pid_t pid = fork ();
-  if (pid < 0)
-    return -1;
-  else if (pid > 0)
-    _exit (0);
-  else {
-    pid = setsid ();
-    if (pid < 0)
-      return -1;
-
-    pid = fork ();
-    if (pid < 0)
-      return -1;
-    else if (pid > 0)
-      _exit (0);
-    else {
-
-      if (!nochdir)
-        if (chdir ("/") < 0)
-          return -1;
-
-      umask ((mode_t) 0);
-
-      if (!noclose) {
-        /* send stdin, stdout, and stderr to /dev/null */
-        int i;
-        int fd = open ("/dev/null", O_RDWR, 0);
-        if (fd < 0)
-          fprintf (stderr, "unable to open /dev/null: %s\n",
-                   tr_strerror (errno));
-        for (i = 0; i < 3; ++i) {
-          if (close (i))
-            return -1;
-          dup2 (fd, i);
-        }
-        close (fd);
-      }
+       pid_t pid;
+       int devnull;
 
-      return 0;
-    }
+  if ((pid=fork()) < 0) return pid;
+  else if (pid > 0) _exit(0);
+  if (!nochdir) chdir("/");
+  if (!noclose) {
+    devnull = open("/dev/null",O_NDELAY|O_RDWR);
+    dup2(devnull,0);
+    close(devnull);
   }
-#else /* USE_NO_DAEMON */
+  pid=setsid();
+  if (pid<0) return -1;
   return 0;
-#endif
 }
 
+
+
+
 static const char *getConfigDir (int argc, const char **argv) {
   int c;
   const char *configDir = NULL;

Attachments (1)

tr_daemon.diff (2.2 KB) - added by jordan 11 years ago.
streamlined rewrite of tr_daemon()

Download all attachments as: .zip

Change History (12)

comment:1 Changed 11 years ago by jordan

  • Component changed from Transmission to Daemon
  1. This breaks one of the compile cases by failing on WIN32, which is where USE_NO_DAEMON is used.
  1. What is the benefit of not using the system version of daemon() where available?
  1. tr_daemon() is intended to be a drop-in replacement for daemon() when daemon() is not avaialble. Deviating from the standard daemonization method wrt stdout, stderr, umask, and a second fork seems unwise. Wouldn't your goal be better reached by changing the arguments passed into tr_daemon()?
  1. Those caveats aside, it does look tr_daemon() could be streamlined. That part of this ticket is about how it's implemented, rather than how it's called. Attached is a possible rewrite that tries to cull together the best practices from various daemon() implementations I've found online.

Changed 11 years ago by jordan

streamlined rewrite of tr_daemon()

comment:2 Changed 11 years ago by jordan

  • Summary changed from Fix of daemon code - allow user to redirect stderr stdout separately from backgrounding to allow user to redirect stderr stdout separately from backgrounding

comment:3 follow-up: Changed 11 years ago by Astara

  1. It should have compiled since the code gets produced in all cases, including the Win32 case. I didn't see any calls to the Win32 'WIN32_{InstallService?, RemoveService?, SetServiceCommandLine?, svcstatusupdate}' type calls around, so I thought it might be linked w/a posix emulation layer on win32.
  1. We don't *know* what it does, and our is trivially short, why not guarantee that we know what we are doing when we call, not have some possible random behavior on some system because they added a bit of spice to their call. We likes da uniformity, ya?!
  1. I've never seen a linux daemon use the daemon call. Who uses it? It seems a bit alien to me -- nonstandard. Maybe its more from BSD than System-V or POSIX? I haven't seen it used in any Linux system programs and it doesn't have a 'Win32', in it so I'm pretty sure it's not standard on Windows :-). It's not standard to redirect stdout and stderr to /dev/null on unix or linux that I've seen, except by request (-q/-s).

Often daemons offer an extra offer a "-q" or "-s" switch to be quiet or silent, but those are optional switches, not the default. That doesn't mean that they are -verbose', but that important channels to relay information are not shut down. The program shouldn't normally be 'chatty', but if a serious error is caught, most of the run scripts on SuSE and Sgi's IRIX, trap the stderr and stdout of daemons to look for messages to log. OTOH, if the user is trying to print something out when a certain event happens, or turn on some sort of debugging, they can experience alot of pain when normal output channels have been 'commandeered' away from their standard locations.

As for doing 2 forks -- why? Do you know why they code does that? Is it something we even need to care about w/transmission? I.e. after flipping into a new sid, the first 'open' term that's opened for i/o by transmission would 'grab' that terminal for transmission and make transmission a 'tty leader'. Yeah rah! But AFAIK, the code doesn't open any TTY devices unless the user was to try to log directly to /dev/tty or something -- but they'd have to launch it with redirection to some 'unused' terminal, since any terminal that already has a process attached to it wouldn't be available for adoption.

But if worried about such, you could use the appropriate ioctl and detach from the terminal -- that would do the same thing. I.e.:

#ifdef TIOCNOTTY
    if ((i = open("/dev/tty", O_RDWR | O_TEXT)) >= 0) {
        ioctl(i, TIOCNOTTY, NULL);
        close(i);
    }
#endif
  1. tr_daemon is only used by this 1 program. It's not designed as a general purpose library, it was modeled AFTER some daemon routine that is not a particularly useful nor standard module, in the system programs I've been exposed to in my ~22 years with unix + linux, but I'm certain I've been exposed to a drop-in-the-bucket of what's out there.

I've often found that 'best practices' -- aren't and more often they are best marketed. ;-)

comment:4 in reply to: ↑ 3 Changed 11 years ago by jordan

Replying to Astara:

  1. It should have compiled since the code gets produced in all cases, including the Win32 case.

...including system calls missing in win32.

  1. We don't *know* what it does

We don't have daemon(3) manpages?

We likes da uniformity, ya?!

Transmission uses native implementations where available. look in utils.c to see that over and over.

  1. I've never seen a linux daemon use the daemon call. Who uses it?

Who doesn't use it?

It's not standard to redirect stdout and stderr to /dev/null on unix or linux that I've seen, except by request (-q/-s).

...which is why I asked about changing the arguments passed into tr_daemon() rather than the implementation of tr_daemon().

  1. tr_daemon is only used by this 1 program. It's not designed as a general purpose library

I'm pretty sure I know why I wrote tr_daemon() as a wrapper with an API signature identical to daemon(). I think it might be better to replace the homebrew code with an #error directive just to see if any platforms don't have daemon(). Even uClibc has it these days...

comment:5 Changed 11 years ago by jordan

...ah, but Solaris doesn't. :)

So, transmission-daemon will keep the "wrapper" approach...

comment:6 follow-up: Changed 11 years ago by Astara

Sun adopting Solaris over SunOS, marked the start of its decline. It's now out of business and was bought up by a database company -- not an OS company. That's hardly a shining example of 'best practices'.

From the linux programmers manual (http://homepages.cwi.nl/~aeb/linux/man2html/man4/tty.4.html), under it's entry for 'tty':

TIOCNOTTY Detach the current process from its controlling terminal.

If the process is the session leader, then SIGHUP and SIGCONT signals are sent to the foreground process group and all processes in the current session lose their controlling tty.

This ioctl() call only works on file descriptors connected to /dev/tty. It is used by daemon processes when they are invoked by a user at a terminal. The process attempts to open /dev/tty. If the open succeeds, it detaches itself from the terminal by using TIOCNOTTY, while if the open fails, it is obviously not attached to a terminal and does not need to detach itself.

From the http://nixdoc.net/images/nixdoc4.png (UniXdoc, *nix Documentation Project)'s page on /dev/tty (http://nixdoc.net/man-pages/Linux/man4/tty.4.html):

In addition to the ioctl() requests supported by the device that tty refers to, the following ioctl() request is supported:

TIOCNOTTY [Toc] [Back]

Detach the current process from its controlling terminal, and remove it from its current process group, without attaching it to a new process group (that is, set its process group ID to zero). This ioctl() call only works on file descriptors connected to /dev/tty; this is used by daemon processes when they are invoked by a user at a terminal. The process attempts to open /dev/tty; if the open succeeds, it detaches itself from the terminal by using TIOCNOTTY, while if the open fails, it is obviously not attached to a terminal and does not need to detach itself.

You can find similar text many places around the net. Considering your statement that you looked.

As for your response to my asking for who used it? You couldn't come up with 1 example. Instead you resort to asking me to name off all of the linux daemons that exist -- squid, all the linux-kernel daemons, samba, bind, to name a few -- ones that are among the most widely used. Several of them don't even call 'setsid', as it's a relatively new call that's only existed for about 10 years or so. It is of questionable usefulness and seems most useful to bean counters.

I'm guessing that computer science wasn't your major at school? What did you major in?

Note -- I'm suggesting the call be renamed 'tr_daemonize' so there's no confusion about its intention -- that it is not intended to be a replacement for the 'daemon' call, as I don't see any need to use the daemon call at all.

Even in your revised code, you first do a loop to close the file descriptors and then a 2nd loop to use 'dup2' to copy over the descriptor from /dev/null.

What's suprising to me is how many people don't know what the functions they are calling actually do. They just use them -- often copying them from someplace else without understanding what's going on.

dup2(2) automatically closes the targeted file descriptor, so all the 'closes' are superfluous.

comment:7 in reply to: ↑ 6 Changed 11 years ago by jordan

  • Resolution set to invalid
  • Status changed from new to closed

Replying to Astara:

Sun adopting Solaris over SunOS, marked the start of its decline. It's now out of business and was bought up by a database company -- not an OS company. That's hardly a shining example of 'best practices'.

Irrelevant noise. Also, who said Solaris was "best practices"?


From the linux programmers manual (http://homepages.cwi.nl/~aeb/linux/man2html/man4/tty.4.html), under it's entry for 'tty':

TIOCNOTTY Detach the current process from its controlling terminal.

If the process is the session leader, then SIGHUP and SIGCONT signals are sent to the foreground process group and all processes in the current session lose their controlling tty.

This ioctl() call only works on file descriptors connected to /dev/tty. It is used by daemon processes when they are invoked by a user at a terminal. The process attempts to open /dev/tty. If the open succeeds, it detaches itself from the terminal by using TIOCNOTTY, while if the open fails, it is obviously not attached to a terminal and does not need to detach itself.

From the http://nixdoc.net/images/nixdoc4.png (UniXdoc, *nix Documentation Project)'s page on /dev/tty (http://nixdoc.net/man-pages/Linux/man4/tty.4.html):

In addition to the ioctl() requests supported by the device that tty refers to, the following ioctl() request is supported:

TIOCNOTTY [Toc] [Back]

Detach the current process from its controlling terminal, and remove it from its current process group, without attaching it to a new process group (that is, set its process group ID to zero). This ioctl() call only works on file descriptors connected to /dev/tty; this is used by daemon processes when they are invoked by a user at a terminal. The process attempts to open /dev/tty; if the open succeeds, it detaches itself from the terminal by using TIOCNOTTY, while if the open fails, it is obviously not attached to a terminal and does not need to detach itself.

You can find similar text many places around the net. Considering your statement that you looked.

I'm guessing that computer science wasn't your major at school? What did you major in?

Apache httpd doesn't use /dev/tty, but it does pretty much the same thing transmission-daemon does... including setting stdin, stdout, and stderr to /dev/null, which is what made you file this ticket... and calling fork() twice, so I guess the people over at Apache didn't major in CS either.. but hell, who uses Apache anyway. Oh wait, everyone does.

Note -- I'm suggesting the call be renamed 'tr_daemonize' so there's no confusion about its intention -- that it is not intended to be a replacement for the 'daemon' call

Except it is intended as a replacement for the 'daemon' call.

as I don't see any need to use the daemon call at all.

You don't like the call, which is not the same thing as it being wrong. Maybe you should go file tickets with Apache telling them that they're wrong, and with glibc telling them to remove the daemon() function since you've never seen anyone (except for transmission-daemon) use it.

comment:8 Changed 11 years ago by Astara

You realize that you pointed to a case that ALSO doesn't use daemonize, and in the code you presented, doesn't protect itself against becoming a 'process group leader' (which is what this whole double fork thing was about) -- so they don't seem to think it is a big option *either* -- which is what I said at the beginning...I said if you are really concerned, you could use the ioctl and be more efficient about it -- but apache doesn't do a double fork -- so far that 0 for 5 using daemon or double forks as you claim are 'best practices'.

Apache is 'fine'...they don't use daemon, they don't implement their own version of daemon, they don't double fork.

They do say right in the code that duping /dev/null to the 'std's is temporary, since they reopen it again later with the *real* destination -- an error log file.

That's what I said we should do -- use stdout and stderr as channels to communicate problems.

Now you can go the route apache does and implement separate arguments to tell the program where to direct stdout and stdin, then you can dup devnull to them just like apache, then open them to the files wanted by the user -- OR you could just leave them alone and let the user direct them to the error files using command redirection.

I take the simpler approach -- don't touch them unless I have to -- no need to implement separate command line switches for things that can be set in the environment that transmission inherits. Things like the userid, groupid, umask, and output file descriptors are all things transmission inherits from the environment -- and they can be set by someone who wants control over those things by explicitly setting them before transmission executes.

OR you can ADD arguments/options to transmission's command line to set all of those things explicitly, which I think is silly.

Sorry about asking about your major, but it's just that your way of thinking was one of following examples of others but not really knowing why they did it or what is best for transmission and how it is used -- and it's frustrating that you just believe something should be done in a certain way just because they did it that way.

Apache has years more maturity behind it than transmission does. They probably don't have apache devs asking users to send them various debug logs as often, if at all. It's not a bad thing. I'm trying to make things easiest for the greatest group of people to have the most flexibility possible. Giving users flexibility and options are what good programs are for. I'm all for giving more control to the users who want to take advantage of it -- I want them to be able to use it in all sorts of ways I might not think of -- so I try to leave many of these options for the users. This doesn't have to be a you vs. me thing that you are creating it to be. In transmission's case, it would make more sense to have a second switch to shut off output.

I also see no reason to throw away the user's security. Can you explain why you are deliberately throwing away the user's set security? Are you anti-security? Why not use the umask set by the user before transmission is invoked? Instead, transmission seems bent on harassing the user to do useless things -- change permission bits back to the desired ones, not put it in background just to get output, forcing them to verify files that don't need verifying... Many of the decisions of the transmission authors seem to be about controlling the users and limiting or removing choice rather than empowering them. Why?

Historically, the most successful products are the ones that have let users add-on, and given them the most choice... Why not pursue that course here?

So far you haven't shown me anyone who uses the daemon call or that forks twice -- I believe you when you say there are programs that use them -- there are programs that do everything in existence, but I was just saying it wasn't that common and both your evidence and my own seems to bear that out. Stop making everything a fight about you vs. me. You can be on top...I don't care - just let me make transmission be better. I really don't want to make this a competition but a cooperation. I don't like to compete -- it drains my energies -- I spent most of the day today in bed because of fatigue. Constantly fighting with you doesn't help.

comment:9 follow-up: Changed 11 years ago by jordan

I didn't see your last comment until now.

We've had out disagreements -- in fact, have we agreed on anything? -- but I'm sorry that you weren't feeling well. I hope you're better now.

Anyway, to the question at hand:

  • I disagree (and resent) your "are you anti-security?" question because of the umask(0) call. I got that line from several sources and don't think that The Linux Daemon HOWTO and Doug Potter and The Python Cookbook are "anti-security" either. However, after reviewing the code, it seems the umask() call is unnecessary anyway because tr_sessionInit() calls umask() from the values in settings.json. So I've removed the call here.
  • As you suggested, the close() calls have been removed because dup2() makes them redundant.
  • tr_daemon() still closes stdin, stdout, and stderr when the noclose argument is false, because that is what glibc's daemon() function does (and so do the other three links above). You seem to be bothered by Transmission's use of daemon(), but you haven't actually said where it's broken. So unless there is a bug in glibc's daemon() that I've propagated over into tr_daemon(), that code will remain as-is.

The latest implementation of tr_daemon() can be found here.

You probably are not happy with the outcome of this ticket, but I hope this answers some of your questions. I hope you get to feeling better soon.

comment:10 in reply to: ↑ 9 ; follow-up: Changed 11 years ago by Astara

Replying to jordan:

I didn't see your last comment until now.

We've had out disagreements -- in fact, have we agreed on anything

---

You do seem to find an opposing reason to almost any suggestion I come up with. The result being that you really make me wonder if there is any point to trying to improve this product, or if it is 'unimprovable'. I usually have a philosophy of trying to *please* the users of my programs and find a way to give them what they want - it may not be exactly the way they envisioned it, but I usually find a way to make them happy, cuz, I like happy users. If it isn't something I want to provide directly in a program, I try to provide some way for them to add their own extension to do it. I believe in empowering the user to make the product -- to customize the product to their exact needs -- not trying to tell them why they are wrong and should do it my way.

You (and the other transmission developers, I've noted) spend more time telling users 'no' and that's not your 'vision' and 'this isn't your product', and if that's what you want, then this isn't it. It is as if you really don't care about what the users want or how they want to do things -- you are providing 'your' program 'this' way, and if they don't like it, adapt. I find that point of view alien. I don't always adapt to what is wanted, but I usually tend to go out of my way to be flexible as my initial offering, then (for better or worse) tend to incorporate their answer style into my own.

With some people this works well -- maybe too well (as I can get overwhelmed w/work far too easily), but with others it has the opposite effect -- even with code submissions, they are rejected --- the result being very little give-n-take on either side (or none) resulting in deadlock. I usually give up and wonder off after a time, until I run into another problem, then often try again, hoping for a different result, occasionally, with some projects this works, but with some, 'history' becomes more important than the 'now' and dictates result. I, *generally*, try to ignore a person's past behavior unless they bring it into the current discussion. The fact that they were annoying 'then' is irrelevant to the current issue, as far as I'm concerned.

? -- but I'm sorry that you weren't feeling well. I hope you're better now. --- Thanks, but unfortunately when my back goes out, it can be as long as multiple weeks. When I'm lucky, I recover the next day. But because it's unpredictable, is why I can't work (I can't keep a schedule with problems like this). Considering the trauma my back has gone through in the past several weeks I shouldn't be surprised. I always try to do more than I 'should'...as you can imagine, I'm not good with limitations -- especially those imposed by my own body.

Anyway, to the question at hand

  • I disagree (and resent) your "are you anti-security?" question because of the umask(0) call. I got that line from several sources...

--- Just because *lemmings* do something doesn't make it right. _You_ have to understand *why* and consider if it is appropriate to the circumstance, you are ultimately responsible for what it does -- you can't point to code and say I did it because 'they' did it. 90% of the time, the code out there is 'crap' -- that includes "best practices code" -- the "best" in the field OFTEN aren't. Case in point is the recent debacle concerning "security experts"[sic] HBGary, that are (were?) used by the CIA among others (http://it.slashdot.org/story/11/02/16/1459204/Attacked-By-Anonymous-HBGary-Pulls-Out-of-RSA). They were preaching about best practices to everyone, yet as "anonymous's" attack on them showed, they were caught by about 6-8 DIFFERENT TYPES of vulnerabilities. They weren't just *hacked*, they were hacked in so many *different* ways -- any one of which would have been sufficient for the purpose of 'hacking' them, but to demonstrate that the *BEST PRACTICES* types were broken in so many ways it was pathetically easy to take them down by one of several means.

Please/ bare with me, as I explain some security philosophy that is known to be true, but not very well liked liked, can't be easily defended against, and is therefore given very little press. It doesn't fit into the corporate 'rubber stamp', security-as-widget philosophy, where you can just buy a lock and put it on your house and think it secure (ignoring windows opened for air...for example). But there is the concept of the system of programs and malware as an ecosystem.

If all the programs 5 'well accepted security practices', all malware operators have to do is attack those 5 methods and find a crack. They may find one for each method. Where they don't succeed is with the program that takes an unknown route to security. It may even have 'holes' that are more gaping than the well published methods (though this isn't desirable and would be bad! ;-)), but because it's an unknown, the attackers can't use any known methods on it. They have to do research just to get into that 1 program. Now if you have 1000's of programs that each use those 5, or better, if only 1-2 of those methods become popular, then attackers can succeed by going after the best published, adopted security methods. Anyone who doesn't use one of those popular methods will be safer. In the same way Apple and Linux users have been relatively ignored by malware writers compared to the windows market -- the Windows market is where a break will get you the most bang for the buck.

Just like in biology, a virus can take out several hosts that have similar 'immune systems' (that use the same security methodology), those hosts that used different security methodologies and didn't copy from others will be the ones that will be more likely to remain secure.

The point being that 'cookie-cutter' solutions copied from the best of a group are also, often, the 'lowest common denominator' solutions that's incorporate the least knowledge about the program they are running in and are usually, therefore, the least efficient and least applicable to the specific program they are running with. I.e. they are nice in a college level theory class, but not so great in the real world. So I don't put much weight in 'stock' solutions of 'simple' code, and you should consider not doing so as well.

==================================

Now, setting aside philosophical discussions, it appears you did the 'right thing' (IMO), and analyzed the code and found that the umask really wasn't necessary:

However, after reviewing the code, it seems the umask() call is unnecessary anyway because tr_sessionInit() calls umask() from the values in settings.json. So I've removed the call here.

However there's a problem somewhere: # grep umask settings.json

"umask": 0,

I have 'umask': 0, but transmission constantly sets file permissions to 600, indicating it is using a 0600 or 0700. I.e. it doesn't seem to be using the mask in the settings.json file. So there is still a problem in that regard.

  • As you suggested, the close() calls have been removed because dup2() makes them redundant.

This is a perfect example of the intelligence behind 'best practices'. 'Best practices' copied all over the place (apparently), show a close call being made on file descriptors that are coded by 'dup2'. What this proves is the *lack* of knowledge of the person who

wrote the original example and by HOW MANY PEOPLE simply copied this BAD PRACTICE into their code -- only to have *someone* (gee, who am I referring to), then call it them examples of 'best practice'.

This was obviously a case of bad practice promulgated because people simply copied the

code.

  • tr_daemon() still closes stdin, stdout, and stderr when the noclose argument is false,

I'm saying for the daemon program NOT to use the 'daemon' call or attempt to mimic it. It is a poor match for the functionality needed and wanted in a daemon despite the name of the routine. Calling the routing 'daemon' doesn't mean it's is a good fit for daemon programs. It could easily be a sample taken from a college level text book.

In 30+ years of industry experience, I've never once seen a daemon program use the 'daemon' call. It's a call for people too lazy or inexperience to know what they want -- which isn't what transmission should be using.

background, logging to syslog or a file, and turning off stderr&out should all be separate operations.. There should be NO reason to turn off stderr and stdout if a log file is provided. In fact there is every reason to leave them *open* because should be no output. Closing them is *hiding* bugs -- hiding output that shouldn't be there. You never want to write code to *hide* bugs. stdout would be for program signon/off statistics if applicable (or not, if silenced with '-q'). If logging is directed to syslog or a file, then stderr should also have nothing on it. If it does, then you really DO want to see it -- not have it go to '/dev/null' -- since you want to find out why it didn't go to a log file. Maybe it was a core-dump message that wouldn't normally be logged, but with stderr closed, who knows?

Turning off stdout/stderr is *bad practice* just like closing the file handles before the dup2 call.

Think about it. If there is not output, why close them? If there is output, then you want to know what it is...either way, you don't dup to /dev/null.

You probably are not happy with the outcome of this ticket, but I hope this answers some of your questions. I hope you get to feeling better soon.

--- Hopefully you'll understand why I don't approve of 'best[sic] practices'. They are meant to hide things and cover up mistakes and make debugging hard. And thanks for the sentiments....I hate feeling 'off'...brain all fogged -- making it hard to concentrate for any length of time, no energy, intermittent pain throughout ... a real drag!

comment:11 in reply to: ↑ 10 Changed 11 years ago by jordan

Replying to Astara:

You do seem to find an opposing reason to almost any suggestion I come up with.

You might find yourself making better headway if you:

  • Come up with better suggestions (guess what? *still* nobody else has complained about torrent files that begin with a "." because they never happen...)
  • State them clearly and concisely and provide evidence (You personally have written literally over 10,000 words in your three "tracker stops announcing" tickets but have yet to provide even one useful log)
  • Stop insulting the developers ("I'm guessing that computer science wasn't your major at school?")

Anyway we've been over this ground before so why repeat myself.

Note: See TracTickets for help on using tickets.