+/*
+ * Called here to send the appropriate commands to the SD
+ * to do truncate on purge.
+ */
+static void truncate_volume(UAContext *ua, MEDIA_DBR *mr,
+ char *pool, char *storage,
+ int drive, BSOCK *sd)
+{
+ bool ok = false;
+ uint64_t VolBytes = 0;
+ uint64_t VolABytes = 0;
+ uint32_t VolType = 0;
+
+ if (!mr->Recycle) {
+ return;
+ }
+
+ /* Do it only if action on purge = truncate is set */
+ if (!(mr->ActionOnPurge & ON_PURGE_TRUNCATE)) {
+ ua->error_msg(_("\nThe option \"Action On Purge = Truncate\" was not defined in the Pool resource.\n"
+ "Unable to truncate volume \"%s\"\n"), mr->VolumeName);
+ return;
+ }
+
+ /*
+ * Send the command to truncate the volume after purge. If this feature
+ * is disabled for the specific device, this will be a no-op.
+ */
+
+ /* Protect us from spaces */
+ bash_spaces(mr->VolumeName);
+ bash_spaces(mr->MediaType);
+ bash_spaces(pool);
+ bash_spaces(storage);
+
+ /* Do it by relabeling the Volume, which truncates it */
+ sd->fsend("relabel %s OldName=%s NewName=%s PoolName=%s "
+ "MediaType=%s Slot=%d drive=%d\n",
+ storage,
+ mr->VolumeName, mr->VolumeName,
+ pool, mr->MediaType, mr->Slot, drive);
+
+ unbash_spaces(mr->VolumeName);
+ unbash_spaces(mr->MediaType);
+ unbash_spaces(pool);
+ unbash_spaces(storage);
+
+ /* Check for valid response. With cloud volumes, the upload of the part.1 can
+ * generate a dir_update_volume_info() message that is handled by bget_dirmsg()
+ */
+ while (bget_dirmsg(sd) >= 0) {
+ ua->send_msg("%s", sd->msg);
+ if (sscanf(sd->msg, "3000 OK label. VolBytes=%llu VolABytes=%lld VolType=%d ",
+ &VolBytes, &VolABytes, &VolType) == 3) {
+
+ ok=true;
+ mr->VolBytes = VolBytes;
+ mr->VolABytes = VolABytes;
+ mr->VolType = VolType;
+ mr->VolFiles = 0;
+ mr->VolParts = 1;
+ mr->VolCloudParts = 0;
+ mr->LastPartBytes = VolBytes;
+
+ set_storageid_in_mr(NULL, mr);
+ if (!db_update_media_record(ua->jcr, ua->db, mr)) {
+ ua->error_msg(_("Can't update volume size in the catalog\n"));
+ }
+ ua->send_msg(_("The volume \"%s\" has been truncated\n"), mr->VolumeName);
+ }
+ }
+ if (!ok) {
+ ua->warning_msg(_("Unable to truncate volume \"%s\"\n"), mr->VolumeName);
+ }
+}
+
+/*
+ * Implement Bacula bconsole command purge action
+ * purge action=truncate pool= volume= storage= mediatype=
+ * or
+ * truncate [cache] pool= volume= storage= mediatype=
+ *
+ * If the keyword "cache: is present, then we use the truncate
+ * command rather than relabel so that the driver can decide
+ * whether or not it wants to truncate. Note: only the
+ * Cloud driver permits truncating the cache.
+ *
+ * Note, later we might want to rename this action_on_purge_cmd() as
+ * was the original, but only if we add additional actions such as
+ * erase, ... For the moment, we only do a truncate.
+ *
+ */
+int truncate_cmd(UAContext *ua, const char *cmd)
+{
+ int drive = -1;
+ int nb = 0;
+ uint32_t *results = NULL;
+ const char *action = "truncate";
+ MEDIA_DBR mr;
+ POOL_DBR pr;
+ BSOCK *sd;
+ char storage[MAX_NAME_LENGTH];
+
+ if (find_arg(ua, "cache") > 0) {
+ return cloud_volumes_cmd(ua, cmd, "truncate cache");
+ }
+
+ memset(&pr, 0, sizeof(pr));
+
+ /*
+ * Look for all Purged volumes that can be recycled, are enabled and
+ * have more than 1,000 bytes (i.e. actually have data).
+ */
+ mr.Recycle = 1;
+ mr.Enabled = 1;
+ mr.VolBytes = 1000;
+ bstrncpy(mr.VolStatus, "Purged", sizeof(mr.VolStatus));
+ /* Get list of volumes to truncate */
+ if (!scan_storage_cmd(ua, cmd, true, /* allfrompool */
+ &drive, &mr, &pr, &action, storage, &nb, &results)) {
+ goto bail_out;
+ }
+
+ if ((sd=open_sd_bsock(ua)) == NULL) {
+ Dmsg0(100, "Can't open connection to sd\n");
+ goto bail_out;
+ }
+
+ /*
+ * Loop over the candidate Volumes and actually truncate them
+ */
+ for (int i=0; i < nb; i++) {
+ mr.clear();
+ mr.MediaId = results[i];
+ if (db_get_media_record(ua->jcr, ua->db, &mr)) {
+ if (strcasecmp(mr.VolStatus, "Purged") != 0) {
+ ua->send_msg(_("Truncate Volume \"%s\" skipped. Status is \"%s\", but must be \"Purged\".\n"),
+ mr.VolumeName, mr.VolStatus);
+ continue;
+ }
+ if (drive < 0) {
+ STORE *store = (STORE*)GetResWithName(R_STORAGE, storage);
+ drive = get_storage_drive(ua, store);
+ }
+
+ /* Must select Pool if not already done */
+ if (pr.PoolId == 0) {
+ pr.PoolId = mr.PoolId;
+ if (!db_get_pool_record(ua->jcr, ua->db, &pr)) {
+ goto bail_out; /* free allocated memory */
+ }
+ }
+ if (strcasecmp("truncate", action) == 0) {
+ truncate_volume(ua, &mr, pr.Name, storage,
+ drive, sd);
+ }
+ } else {
+ Dmsg1(0, "Can't find MediaId=%lu\n", mr.MediaId);
+ }
+ }
+
+bail_out:
+ close_db(ua);
+ close_sd_bsock(ua);
+ ua->jcr->wstore = NULL;
+ if (results) {
+ free(results);
+ }
+
+ return 1;
+}
+