1 /*
2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3 *
4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5 * and others.
6 *
7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8 * Portions Copyright (C) 1989-1992, Brian Berliner
9 *
10 * You may distribute under the terms of the GNU General Public License as
11 * specified in the README file that comes with the CVS source distribution.
12 *
13 * Remove a File
14 *
15 * Removes entries from the present version. The entries will be removed from
16 * the RCS repository upon the next "commit".
17 *
18 * "remove" accepts no options, only file names that are to be removed. The
19 * file must not exist in the current directory for "remove" to work
20 * correctly.
21 */
22 #include <sys/cdefs.h>
23 __RCSID("$NetBSD: remove.c,v 1.4 2016/05/17 14:00:09 christos Exp $");
24
25 #include "cvs.h"
26
27 #ifdef CLIENT_SUPPORT
28 static int remove_force_fileproc (void *callerdat,
29 struct file_info *finfo);
30 #endif
31 static int remove_fileproc (void *callerdat, struct file_info *finfo);
32 static Dtype remove_dirproc (void *callerdat, const char *dir,
33 const char *repos, const char *update_dir,
34 List *entries);
35
36 static int force;
37 static int local;
38 static int removed_files;
39 static int existing_files;
40
41 static const char *const remove_usage[] =
42 {
43 "Usage: %s %s [-flR] [files...]\n",
44 "\t-f\tDelete the file before removing it.\n",
45 "\t-l\tProcess this directory only (not recursive).\n",
46 "\t-R\tProcess directories recursively.\n",
47 "(Specify the --help global option for a list of other help options)\n",
48 NULL
49 };
50
51 int
cvsremove(int argc,char ** argv)52 cvsremove (int argc, char **argv)
53 {
54 int c, err;
55
56 if (argc == -1)
57 usage (remove_usage);
58
59 getoptreset ();
60 while ((c = getopt (argc, argv, "+flR")) != -1)
61 {
62 switch (c)
63 {
64 case 'f':
65 force = 1;
66 break;
67 case 'l':
68 local = 1;
69 break;
70 case 'R':
71 local = 0;
72 break;
73 case '?':
74 default:
75 usage (remove_usage);
76 break;
77 }
78 }
79 argc -= optind;
80 argv += optind;
81
82 wrap_setup ();
83
84 #ifdef CLIENT_SUPPORT
85 if (current_parsed_root->isremote) {
86 /* Call expand_wild so that the local removal of files will
87 work. It's ok to do it always because we have to send the
88 file names expanded anyway. */
89 expand_wild (argc, argv, &argc, &argv);
90
91 if (force)
92 {
93 if (!noexec)
94 {
95 start_recursion (remove_force_fileproc, NULL, NULL, NULL,
96 NULL, argc, argv, local, W_LOCAL,
97 0, CVS_LOCK_NONE, NULL, 0, NULL);
98 }
99 /* else FIXME should probably act as if the file doesn't exist
100 in doing the following checks. */
101 }
102
103 start_server ();
104 ign_setup ();
105 if (local)
106 send_arg("-l");
107 send_arg ("--");
108 /* FIXME: Can't we set SEND_NO_CONTENTS here? Needs investigation. */
109 send_files (argc, argv, local, 0, 0);
110 send_file_names (argc, argv, 0);
111 free_names (&argc, argv);
112 send_to_server ("remove\012", 0);
113 return get_responses_and_close ();
114 }
115 #endif
116
117 /* start the recursion processor */
118 err = start_recursion (remove_fileproc, NULL, remove_dirproc, NULL,
119 NULL, argc, argv, local, W_LOCAL, 0,
120 CVS_LOCK_READ, NULL, 1, NULL);
121
122 if (removed_files && !really_quiet)
123 error (0, 0, "use `%s commit' to remove %s permanently", program_name,
124 (removed_files == 1) ? "this file" : "these files");
125
126 if (existing_files)
127 error (0, 0,
128 ((existing_files == 1) ?
129 "%d file exists; remove it first" :
130 "%d files exist; remove them first"),
131 existing_files);
132
133 return (err);
134 }
135
136 #ifdef CLIENT_SUPPORT
137
138 /*
139 * This is called via start_recursion if we are running as the client
140 * and the -f option was used. We just physically remove the file.
141 */
142
143 /*ARGSUSED*/
144 static int
remove_force_fileproc(void * callerdat,struct file_info * finfo)145 remove_force_fileproc (void *callerdat, struct file_info *finfo)
146 {
147 if (CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
148 error (0, errno, "unable to remove %s", finfo->fullname);
149 return 0;
150 }
151
152 #endif
153
154 /*
155 * remove the file, only if it has already been physically removed
156 */
157 /* ARGSUSED */
158 static int
remove_fileproc(void * callerdat,struct file_info * finfo)159 remove_fileproc (void *callerdat, struct file_info *finfo)
160 {
161 Vers_TS *vers;
162
163 if (force)
164 {
165 if (!noexec)
166 {
167 if ( CVS_UNLINK (finfo->file) < 0 && ! existence_error (errno))
168 {
169 error (0, errno, "unable to remove %s", finfo->fullname);
170 }
171 }
172 /* else FIXME should probably act as if the file doesn't exist
173 in doing the following checks. */
174 }
175
176 vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0);
177
178 if (vers->ts_user != NULL)
179 {
180 existing_files++;
181 if (!quiet)
182 error (0, 0, "file `%s' still in working directory",
183 finfo->fullname);
184 }
185 else if (vers->vn_user == NULL)
186 {
187 if (!quiet)
188 error (0, 0, "nothing known about `%s'", finfo->fullname);
189 }
190 else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
191 {
192 char *fname;
193
194 /*
195 * It's a file that has been added, but not commited yet. So,
196 * remove the ,t file for it and scratch it from the
197 * entries file. */
198 Scratch_Entry (finfo->entries, finfo->file);
199 fname = Xasprintf ("%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
200 if (unlink_file (fname) < 0
201 && !existence_error (errno))
202 error (0, errno, "cannot remove %s", CVSEXT_LOG);
203 if (!quiet)
204 error (0, 0, "removed `%s'", finfo->fullname);
205
206 #ifdef SERVER_SUPPORT
207 if (server_active)
208 server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
209 #endif
210 free (fname);
211 }
212 else if (vers->vn_user[0] == '-')
213 {
214 if (!quiet)
215 error (0, 0, "file `%s' already scheduled for removal",
216 finfo->fullname);
217 }
218 else if (vers->tag != NULL && isdigit ((unsigned char) *vers->tag))
219 {
220 /* Commit will just give an error, and so there seems to be
221 little reason to allow the remove. I mean, conflicts that
222 arise out of parallel development are one thing, but conflicts
223 that arise from sticky tags are quite another.
224
225 I would have thought that non-branch sticky tags should be the
226 same but at least now, removing a file with a non-branch sticky
227 tag means to delete the tag from the file. I'm not sure that
228 is a good behavior, but until it is changed, we need to allow
229 it. */
230 error (0, 0, "\
231 cannot remove file `%s' which has a numeric sticky tag of `%s'",
232 finfo->fullname, vers->tag);
233 }
234 else if (vers->date != NULL)
235 {
236 /* Commit will just give an error, and so there seems to be
237 little reason to allow the remove. */
238 error (0, 0, "\
239 cannot remove file `%s' which has a sticky date of `%s'",
240 finfo->fullname, vers->date);
241 }
242 else
243 {
244 char *fname;
245
246 /* cvsacl patch */
247 #ifdef SERVER_SUPPORT
248 if (use_cvs_acl /* && server_active */)
249 {
250 if (!access_allowed (finfo->file, finfo->repository, vers->tag, 7,
251 NULL, NULL, 1))
252 {
253 if (stop_at_first_permission_denied)
254 error (1, 0, "permission denied for %s",
255 Short_Repository (finfo->repository));
256 else
257 error (0, 0, "permission denied for %s/%s",
258 Short_Repository (finfo->repository), finfo->file);
259
260 return (0);
261 }
262 }
263 #endif
264
265 /* Re-register it with a negative version number. */
266 fname = Xasprintf ("-%s", vers->vn_user);
267 Register (finfo->entries, finfo->file, fname, vers->ts_rcs,
268 vers->options, vers->tag, vers->date, vers->ts_conflict);
269 if (!quiet)
270 error (0, 0, "scheduling `%s' for removal", finfo->fullname);
271 removed_files++;
272
273 #ifdef SERVER_SUPPORT
274 if (server_active)
275 server_checked_in (finfo->file, finfo->update_dir, finfo->repository);
276 #endif
277 free (fname);
278 }
279
280 freevers_ts (&vers);
281 return (0);
282 }
283
284
285
286 /*
287 * Print a warm fuzzy message
288 */
289 /* ARGSUSED */
290 static Dtype
remove_dirproc(void * callerdat,const char * dir,const char * repos,const char * update_dir,List * entries)291 remove_dirproc (void *callerdat, const char *dir, const char *repos,
292 const char *update_dir, List *entries)
293 {
294 if (!quiet)
295 error (0, 0, "Removing %s", update_dir);
296 return (R_PROCESS);
297 }
298