1a7c91847Schristos /*
2a7c91847Schristos * Copyright (C) 1994-2005 The Free Software Foundation, Inc.
3a7c91847Schristos *
4a7c91847Schristos * This program is free software; you can redistribute it and/or modify
5a7c91847Schristos * it under the terms of the GNU General Public License as published by
6a7c91847Schristos * the Free Software Foundation; either version 2, or (at your option)
7a7c91847Schristos * any later version.
8a7c91847Schristos *
9a7c91847Schristos * This program is distributed in the hope that it will be useful,
10a7c91847Schristos * but WITHOUT ANY WARRANTY; without even the implied warranty of
11a7c91847Schristos * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12a7c91847Schristos * GNU General Public License for more details.
13a7c91847Schristos */
14*5a6c14c8Schristos #include <sys/cdefs.h>
15*5a6c14c8Schristos __RCSID("$NetBSD: release.c,v 1.3 2016/05/17 14:00:09 christos Exp $");
16a7c91847Schristos
17a7c91847Schristos /*
18a7c91847Schristos * Release: "cancel" a checkout in the history log.
19a7c91847Schristos *
20a7c91847Schristos * - Enter a line in the history log indicating the "release". - If asked to,
21a7c91847Schristos * delete the local working directory.
22a7c91847Schristos */
23a7c91847Schristos
24a7c91847Schristos #include "cvs.h"
25a7c91847Schristos #include "save-cwd.h"
26a7c91847Schristos #include "getline.h"
27a7c91847Schristos #include "yesno.h"
28a7c91847Schristos
29a7c91847Schristos static const char *const release_usage[] =
30a7c91847Schristos {
31a7c91847Schristos "Usage: %s %s [-d] directories...\n",
32a7c91847Schristos "\t-d\tDelete the given directory.\n",
33a7c91847Schristos "(Specify the --help global option for a list of other help options)\n",
34a7c91847Schristos NULL
35a7c91847Schristos };
36a7c91847Schristos
37a7c91847Schristos #ifdef SERVER_SUPPORT
38a7c91847Schristos static int release_server (int argc, char **argv);
39a7c91847Schristos
40a7c91847Schristos /* This is the server side of cvs release. */
41a7c91847Schristos static int
release_server(int argc,char ** argv)42a7c91847Schristos release_server (int argc, char **argv)
43a7c91847Schristos {
44a7c91847Schristos int i;
45a7c91847Schristos
46a7c91847Schristos /* Note that we skip argv[0]. */
47a7c91847Schristos for (i = 1; i < argc; ++i)
48a7c91847Schristos history_write ('F', argv[i], "", argv[i], "");
49a7c91847Schristos return 0;
50a7c91847Schristos }
51a7c91847Schristos
52a7c91847Schristos #endif /* SERVER_SUPPORT */
53a7c91847Schristos
54a7c91847Schristos /* Construct an update command. Be sure to add authentication and
55a7c91847Schristos * encryption if we are using them currently, else our child process may
56a7c91847Schristos * not be able to communicate with the server.
57a7c91847Schristos */
58a7c91847Schristos static FILE *
setup_update_command(pid_t * child_pid)59a7c91847Schristos setup_update_command (pid_t *child_pid)
60a7c91847Schristos {
61a7c91847Schristos int tofd, fromfd;
62a7c91847Schristos
63a7c91847Schristos run_setup (program_path);
64a7c91847Schristos
65a7c91847Schristos #if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
66a7c91847Schristos /* Be sure to add authentication and encryption if we are using them
67a7c91847Schristos * currently, else our child process may not be able to communicate with
68a7c91847Schristos * the server.
69a7c91847Schristos */
70a7c91847Schristos if (cvsauthenticate) run_add_arg ("-a");
71a7c91847Schristos if (cvsencrypt) run_add_arg ("-x");
72a7c91847Schristos #endif
73a7c91847Schristos
74a7c91847Schristos /* Don't really change anything. */
75a7c91847Schristos run_add_arg ("-n");
76a7c91847Schristos
77a7c91847Schristos /* Don't need full verbosity. */
78a7c91847Schristos run_add_arg ("-q");
79a7c91847Schristos
80a7c91847Schristos /* Propogate our CVSROOT. */
81a7c91847Schristos run_add_arg ("-d");
82a7c91847Schristos run_add_arg (original_parsed_root->original);
83a7c91847Schristos
84a7c91847Schristos run_add_arg ("update");
85a7c91847Schristos *child_pid = run_piped (&tofd, &fromfd);
86a7c91847Schristos if (*child_pid < 0)
87a7c91847Schristos error (1, 0, "could not fork server process");
88a7c91847Schristos
89a7c91847Schristos close (tofd);
90a7c91847Schristos
91a7c91847Schristos return fdopen (fromfd, "r");
92a7c91847Schristos }
93a7c91847Schristos
94a7c91847Schristos
95a7c91847Schristos
96a7c91847Schristos static int
close_update_command(FILE * fp,pid_t child_pid)97a7c91847Schristos close_update_command (FILE *fp, pid_t child_pid)
98a7c91847Schristos {
99a7c91847Schristos int status;
100a7c91847Schristos pid_t w;
101a7c91847Schristos
102a7c91847Schristos do
103a7c91847Schristos w = waitpid (child_pid, &status, 0);
104a7c91847Schristos while (w == -1 && errno == EINTR);
105a7c91847Schristos if (w == -1)
106a7c91847Schristos error (1, errno, "waiting for process %d", child_pid);
107a7c91847Schristos
108a7c91847Schristos return status;
109a7c91847Schristos }
110a7c91847Schristos
111a7c91847Schristos
112a7c91847Schristos
113a7c91847Schristos /* There are various things to improve about this implementation:
114a7c91847Schristos
115a7c91847Schristos 1. Using run_popen to run "cvs update" could be replaced by a
116a7c91847Schristos fairly simple start_recursion/classify_file loop--a win for
117a7c91847Schristos portability, performance, and cleanliness. In particular, there is
118a7c91847Schristos no particularly good way to find the right "cvs".
119a7c91847Schristos
120a7c91847Schristos 2. The fact that "cvs update" contacts the server slows things down;
121a7c91847Schristos it undermines the case for using "cvs release" rather than "rm -rf".
122a7c91847Schristos However, for correctly printing "? foo" and correctly handling
123a7c91847Schristos CVSROOTADM_IGNORE, we currently need to contact the server. (One
124a7c91847Schristos idea for how to fix this is to stash a copy of CVSROOTADM_IGNORE in
125a7c91847Schristos the working directories; see comment at base_* in entries.c for a
126a7c91847Schristos few thoughts on that).
127a7c91847Schristos
128a7c91847Schristos 3. Would be nice to take processing things on the client side one step
129a7c91847Schristos further, and making it like edit/unedit in terms of working well if
130a7c91847Schristos disconnected from the network, and then sending a delayed
131a7c91847Schristos notification.
132a7c91847Schristos
133a7c91847Schristos 4. Having separate network turnarounds for the "Notify" request
134a7c91847Schristos which we do as part of unedit, and for the "release" itself, is slow
135a7c91847Schristos and unnecessary. */
136a7c91847Schristos
137a7c91847Schristos int
release(int argc,char ** argv)138a7c91847Schristos release (int argc, char **argv)
139a7c91847Schristos {
140a7c91847Schristos FILE *fp;
141a7c91847Schristos int i, c;
142a7c91847Schristos char *line = NULL;
143a7c91847Schristos size_t line_allocated = 0;
144a7c91847Schristos char *thisarg;
145a7c91847Schristos int arg_start_idx;
146a7c91847Schristos int err = 0;
147a7c91847Schristos short delete_flag = 0;
148a7c91847Schristos struct saved_cwd cwd;
149a7c91847Schristos
150a7c91847Schristos #ifdef SERVER_SUPPORT
151a7c91847Schristos if (server_active)
152a7c91847Schristos return release_server (argc, argv);
153a7c91847Schristos #endif
154a7c91847Schristos
155a7c91847Schristos /* Everything from here on is client or local. */
156a7c91847Schristos if (argc == -1)
157a7c91847Schristos usage (release_usage);
158889c434eSchristos getoptreset ();
159a7c91847Schristos while ((c = getopt (argc, argv, "+Qdq")) != -1)
160a7c91847Schristos {
161a7c91847Schristos switch (c)
162a7c91847Schristos {
163a7c91847Schristos case 'Q':
164a7c91847Schristos case 'q':
165a7c91847Schristos error (1, 0,
166a7c91847Schristos "-q or -Q must be specified before \"%s\"",
167a7c91847Schristos cvs_cmd_name);
168a7c91847Schristos break;
169a7c91847Schristos case 'd':
170a7c91847Schristos delete_flag++;
171a7c91847Schristos break;
172a7c91847Schristos case '?':
173a7c91847Schristos default:
174a7c91847Schristos usage (release_usage);
175a7c91847Schristos break;
176a7c91847Schristos }
177a7c91847Schristos }
178a7c91847Schristos argc -= optind;
179a7c91847Schristos argv += optind;
180a7c91847Schristos
181a7c91847Schristos /* We're going to run "cvs -n -q update" and check its output; if
182a7c91847Schristos * the output is sufficiently unalarming, then we release with no
183a7c91847Schristos * questions asked. Else we prompt, then maybe release.
184a7c91847Schristos * (Well, actually we ask no matter what. Our notion of "sufficiently
185a7c91847Schristos * unalarming" doesn't take into account "? foo.c" files, so it is
186a7c91847Schristos * up to the user to take note of them, at least currently
187a7c91847Schristos * (ignore-193 in testsuite)).
188a7c91847Schristos */
189a7c91847Schristos
190a7c91847Schristos #ifdef CLIENT_SUPPORT
191a7c91847Schristos /* Start the server; we'll close it after looping. */
192a7c91847Schristos if (current_parsed_root->isremote)
193a7c91847Schristos {
194a7c91847Schristos start_server ();
195a7c91847Schristos ign_setup ();
196a7c91847Schristos }
197a7c91847Schristos #endif /* CLIENT_SUPPORT */
198a7c91847Schristos
199a7c91847Schristos /* Remember the directory where "cvs release" was invoked because
200a7c91847Schristos all args are relative to this directory and we chdir around.
201a7c91847Schristos */
202a7c91847Schristos if (save_cwd (&cwd))
203a7c91847Schristos error (1, errno, "Failed to save current directory.");
204a7c91847Schristos
205a7c91847Schristos arg_start_idx = 0;
206a7c91847Schristos
207a7c91847Schristos for (i = arg_start_idx; i < argc; i++)
208a7c91847Schristos {
209a7c91847Schristos thisarg = argv[i];
210a7c91847Schristos
211a7c91847Schristos if (isdir (thisarg))
212a7c91847Schristos {
213a7c91847Schristos if (CVS_CHDIR (thisarg) < 0)
214a7c91847Schristos {
215a7c91847Schristos if (!really_quiet)
216a7c91847Schristos error (0, errno, "can't chdir to: %s", thisarg);
217a7c91847Schristos continue;
218a7c91847Schristos }
219a7c91847Schristos if (!isdir (CVSADM))
220a7c91847Schristos {
221a7c91847Schristos if (!really_quiet)
222a7c91847Schristos error (0, 0, "no repository directory: %s", thisarg);
223a7c91847Schristos if (restore_cwd (&cwd))
224a7c91847Schristos error (1, errno,
225a7c91847Schristos "Failed to restore current directory, `%s'.",
226a7c91847Schristos cwd.name);
227a7c91847Schristos continue;
228a7c91847Schristos }
229a7c91847Schristos }
230a7c91847Schristos else
231a7c91847Schristos {
232a7c91847Schristos if (!really_quiet)
233a7c91847Schristos error (0, 0, "no such directory: %s", thisarg);
234a7c91847Schristos continue;
235a7c91847Schristos }
236a7c91847Schristos
237a7c91847Schristos if (!really_quiet)
238a7c91847Schristos {
239a7c91847Schristos int line_length, status;
240a7c91847Schristos pid_t child_pid;
241a7c91847Schristos
242a7c91847Schristos /* The "release" command piggybacks on "update", which
243a7c91847Schristos does the real work of finding out if anything is not
244a7c91847Schristos up-to-date with the repository. Then "release" prompts
245a7c91847Schristos the user, telling her how many files have been
246a7c91847Schristos modified, and asking if she still wants to do the
247a7c91847Schristos release. */
248a7c91847Schristos fp = setup_update_command (&child_pid);
249a7c91847Schristos if (fp == NULL)
250a7c91847Schristos {
251a7c91847Schristos error (0, 0, "cannot run command:");
252a7c91847Schristos run_print (stderr);
253a7c91847Schristos error (1, 0, "Exiting due to fatal error referenced above.");
254a7c91847Schristos }
255a7c91847Schristos
256a7c91847Schristos c = 0;
257a7c91847Schristos
258a7c91847Schristos while ((line_length = getline (&line, &line_allocated, fp)) >= 0)
259a7c91847Schristos {
260a7c91847Schristos if (strchr ("MARCZ", *line))
261a7c91847Schristos c++;
262a7c91847Schristos (void) fputs (line, stdout);
263a7c91847Schristos }
264a7c91847Schristos if (line_length < 0 && !feof (fp))
265a7c91847Schristos error (0, errno, "cannot read from subprocess");
266a7c91847Schristos
267a7c91847Schristos /* If the update exited with an error, then we just want to
268a7c91847Schristos complain and go on to the next arg. Especially, we do
269a7c91847Schristos not want to delete the local copy, since it's obviously
270a7c91847Schristos not what the user thinks it is. */
271a7c91847Schristos status = close_update_command (fp, child_pid);
272a7c91847Schristos if (status != 0)
273a7c91847Schristos {
274a7c91847Schristos error (0, 0, "unable to release `%s' (%d)", thisarg, status);
275a7c91847Schristos if (restore_cwd (&cwd))
276a7c91847Schristos error (1, errno,
277a7c91847Schristos "Failed to restore current directory, `%s'.",
278a7c91847Schristos cwd.name);
279a7c91847Schristos continue;
280a7c91847Schristos }
281a7c91847Schristos
282a7c91847Schristos printf ("You have [%d] altered files in this repository.\n",
283a7c91847Schristos c);
284a7c91847Schristos
285a7c91847Schristos if (!noexec)
286a7c91847Schristos {
287a7c91847Schristos printf ("Are you sure you want to release %sdirectory `%s': ",
288a7c91847Schristos delete_flag ? "(and delete) " : "", thisarg);
289a7c91847Schristos fflush (stderr);
290a7c91847Schristos fflush (stdout);
291a7c91847Schristos if (!yesno ()) /* "No" */
292a7c91847Schristos {
293a7c91847Schristos (void) fprintf (stderr,
294a7c91847Schristos "** `%s' aborted by user choice.\n",
295a7c91847Schristos cvs_cmd_name);
296a7c91847Schristos if (restore_cwd (&cwd))
297a7c91847Schristos error (1, errno,
298a7c91847Schristos "Failed to restore current directory, `%s'.",
299a7c91847Schristos cwd.name);
300a7c91847Schristos continue;
301a7c91847Schristos }
302a7c91847Schristos }
303a7c91847Schristos else
304a7c91847Schristos {
305a7c91847Schristos if (restore_cwd (&cwd))
306a7c91847Schristos error (1, errno,
307a7c91847Schristos "Failed to restore current directory, `%s'.",
308a7c91847Schristos cwd.name);
309a7c91847Schristos continue;
310a7c91847Schristos }
311a7c91847Schristos }
312a7c91847Schristos
313a7c91847Schristos /* Note: client.c doesn't like to have other code
314a7c91847Schristos changing the current directory on it. So a fair amount
315a7c91847Schristos of effort is needed to make sure it doesn't get confused
316a7c91847Schristos about the directory and (for example) overwrite
317a7c91847Schristos CVS/Entries file in the wrong directory. See release-17
318a7c91847Schristos through release-23. */
319a7c91847Schristos
320a7c91847Schristos if (restore_cwd (&cwd))
321a7c91847Schristos error (1, errno, "Failed to restore current directory, `%s'.",
322a7c91847Schristos cwd.name);
323a7c91847Schristos
324a7c91847Schristos #ifdef CLIENT_SUPPORT
325a7c91847Schristos if (!current_parsed_root->isremote
326a7c91847Schristos || (supported_request ("noop") && supported_request ("Notify")))
327a7c91847Schristos #endif
328a7c91847Schristos {
329a7c91847Schristos int argc = 2;
330a7c91847Schristos char *argv[3];
331a7c91847Schristos argv[0] = "dummy";
332a7c91847Schristos argv[1] = thisarg;
333a7c91847Schristos argv[2] = NULL;
334a7c91847Schristos err += unedit (argc, argv);
335a7c91847Schristos if (restore_cwd (&cwd))
336a7c91847Schristos error (1, errno, "Failed to restore current directory, `%s'.",
337a7c91847Schristos cwd.name);
338a7c91847Schristos }
339a7c91847Schristos
340a7c91847Schristos #ifdef CLIENT_SUPPORT
341a7c91847Schristos if (current_parsed_root->isremote)
342a7c91847Schristos {
343a7c91847Schristos send_to_server ("Argument ", 0);
344a7c91847Schristos send_to_server (thisarg, 0);
345a7c91847Schristos send_to_server ("\012", 1);
346a7c91847Schristos send_to_server ("release\012", 0);
347a7c91847Schristos }
348a7c91847Schristos else
349a7c91847Schristos #endif /* CLIENT_SUPPORT */
350a7c91847Schristos {
351a7c91847Schristos history_write ('F', thisarg, "", thisarg, ""); /* F == Free */
352a7c91847Schristos }
353a7c91847Schristos
354a7c91847Schristos if (delete_flag)
355a7c91847Schristos {
356a7c91847Schristos /* FIXME? Shouldn't this just delete the CVS-controlled
357a7c91847Schristos files and, perhaps, the files that would normally be
358a7c91847Schristos ignored and leave everything else? */
359a7c91847Schristos
360a7c91847Schristos if (unlink_file_dir (thisarg) < 0)
361a7c91847Schristos error (0, errno, "deletion of directory %s failed", thisarg);
362a7c91847Schristos }
363a7c91847Schristos
364a7c91847Schristos #ifdef CLIENT_SUPPORT
365a7c91847Schristos if (current_parsed_root->isremote)
366a7c91847Schristos {
367a7c91847Schristos /* FIXME:
368a7c91847Schristos * Is there a good reason why get_server_responses() isn't
369a7c91847Schristos * responsible for restoring its initial directory itself when
370a7c91847Schristos * finished?
371a7c91847Schristos */
372a7c91847Schristos err += get_server_responses ();
373a7c91847Schristos
374a7c91847Schristos if (restore_cwd (&cwd))
375a7c91847Schristos error (1, errno, "Failed to restore current directory, `%s'.",
376a7c91847Schristos cwd.name);
377a7c91847Schristos }
378a7c91847Schristos #endif /* CLIENT_SUPPORT */
379a7c91847Schristos }
380a7c91847Schristos
381a7c91847Schristos if (restore_cwd (&cwd))
382a7c91847Schristos error (1, errno, "Failed to restore current directory, `%s'.",
383a7c91847Schristos cwd.name);
384a7c91847Schristos free_cwd (&cwd);
385a7c91847Schristos
386a7c91847Schristos #ifdef CLIENT_SUPPORT
387a7c91847Schristos if (current_parsed_root->isremote)
388a7c91847Schristos {
389a7c91847Schristos /* Unfortunately, client.c doesn't offer a way to close
390a7c91847Schristos the connection without waiting for responses. The extra
391a7c91847Schristos network turnaround here is quite unnecessary other than
392a7c91847Schristos that.... */
393a7c91847Schristos send_to_server ("noop\012", 0);
394a7c91847Schristos err += get_responses_and_close ();
395a7c91847Schristos }
396a7c91847Schristos #endif /* CLIENT_SUPPORT */
397a7c91847Schristos
398a7c91847Schristos if (line != NULL)
399a7c91847Schristos free (line);
400a7c91847Schristos return err;
401a7c91847Schristos }
402